// Copyright 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.content.browser; import android.app.Activity; import android.app.SearchManager; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.database.ContentObserver; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Rect; import android.graphics.RectF; import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.ResultReceiver; import android.provider.Browser; import android.provider.Settings; import android.text.Editable; import android.text.TextUtils; import android.util.Log; import android.util.Pair; import android.view.ActionMode; import android.view.HapticFeedbackConstants; import android.view.InputDevice; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.Surface; import android.view.View; import android.view.ViewGroup; import android.view.Window; import android.view.WindowManager; import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityManager.AccessibilityStateChangeListener; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeProvider; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.AbsoluteLayout; import android.widget.FrameLayout; import com.google.common.annotations.VisibleForTesting; import org.chromium.base.CalledByNative; import org.chromium.base.JNINamespace; import org.chromium.base.WeakContext; import org.chromium.content.R; import org.chromium.content.browser.ContentViewGestureHandler.MotionEventDelegate; import org.chromium.content.browser.accessibility.AccessibilityInjector; import org.chromium.content.browser.accessibility.BrowserAccessibilityManager; import org.chromium.content.browser.input.AdapterInputConnection; import org.chromium.content.browser.input.HandleView; import org.chromium.content.browser.input.ImeAdapter; import org.chromium.content.browser.input.ImeAdapter.AdapterInputConnectionFactory; import org.chromium.content.browser.input.InputMethodManagerWrapper; import org.chromium.content.browser.input.InsertionHandleController; import org.chromium.content.browser.input.SelectPopupDialog; import org.chromium.content.browser.input.SelectionHandleController; import org.chromium.content.common.TraceEvent; import org.chromium.ui.ViewAndroid; import org.chromium.ui.ViewAndroidDelegate; import org.chromium.ui.WindowAndroid; import org.chromium.ui.gfx.DeviceDisplayInfo; import java.lang.annotation.Annotation; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet; import java.util.Map; /** * Provides a Java-side 'wrapper' around a WebContent (native) instance. * Contains all the major functionality necessary to manage the lifecycle of a ContentView without * being tied to the view system. */ @JNINamespace("content") public class ContentViewCore implements MotionEventDelegate, NavigationClient, AccessibilityStateChangeListener { /** * Indicates that input events are batched together and delivered just before vsync. */ public static final int INPUT_EVENTS_DELIVERED_AT_VSYNC = 1; /** * Opposite of INPUT_EVENTS_DELIVERED_AT_VSYNC. */ public static final int INPUT_EVENTS_DELIVERED_IMMEDIATELY = 0; private static final String TAG = "ContentViewCore"; // Used to avoid enabling zooming in / out if resulting zooming will // produce little visible difference. private static final float ZOOM_CONTROLS_EPSILON = 0.007f; // Used to represent gestures for long press and long tap. private static final int IS_LONG_PRESS = 1; private static final int IS_LONG_TAP = 2; // Length of the delay (in ms) before fading in handles after the last page movement. private static final int TEXT_HANDLE_FADE_IN_DELAY = 300; // If the embedder adds a JavaScript interface object that contains an indirect reference to // the ContentViewCore, then storing a strong ref to the interface object on the native // side would prevent garbage collection of the ContentViewCore (as that strong ref would // create a new GC root). // For that reason, we store only a weak reference to the interface object on the // native side. However we still need a strong reference on the Java side to // prevent garbage collection if the embedder doesn't maintain their own ref to the // interface object - the Java side ref won't create a new GC root. // This map stores those refernces. We put into the map on addJavaScriptInterface() // and remove from it in removeJavaScriptInterface(). private final Map<String, Object> mJavaScriptInterfaces = new HashMap<String, Object>(); // Additionally, we keep track of all Java bound JS objects that are in use on the // current page to ensure that they are not garbage collected until the page is // navigated. This includes interface objects that have been removed // via the removeJavaScriptInterface API and transient objects returned from methods // on the interface object. Note we use HashSet rather than Set as the native side // expects HashSet (no bindings for interfaces). private final HashSet<Object> mRetainedJavaScriptObjects = new HashSet<Object>(); /** * Interface that consumers of {@link ContentViewCore} must implement to allow the proper * dispatching of view methods through the containing view. * * <p> * All methods with the "super_" prefix should be routed to the parent of the * implementing container view. */ @SuppressWarnings("javadoc") public interface InternalAccessDelegate { /** * @see View#drawChild(Canvas, View, long) */ boolean drawChild(Canvas canvas, View child, long drawingTime); /** * @see View#onKeyUp(keyCode, KeyEvent) */ boolean super_onKeyUp(int keyCode, KeyEvent event); /** * @see View#dispatchKeyEventPreIme(KeyEvent) */ boolean super_dispatchKeyEventPreIme(KeyEvent event); /** * @see View#dispatchKeyEvent(KeyEvent) */ boolean super_dispatchKeyEvent(KeyEvent event); /** * @see View#onGenericMotionEvent(MotionEvent) */ boolean super_onGenericMotionEvent(MotionEvent event); /** * @see View#onConfigurationChanged(Configuration) */ void super_onConfigurationChanged(Configuration newConfig); /** * @see View#onScrollChanged(int, int, int, int) */ void onScrollChanged(int lPix, int tPix, int oldlPix, int oldtPix); /** * @see View#awakenScrollBars() */ boolean awakenScrollBars(); /** * @see View#awakenScrollBars(int, boolean) */ boolean super_awakenScrollBars(int startDelay, boolean invalidate); } /** * An interface that allows the embedder to be notified of events and state changes related to * gesture processing. */ public interface GestureStateListener { /** * Called when the pinch gesture starts. */ void onPinchGestureStart(); /** * Called when the pinch gesture ends. */ void onPinchGestureEnd(); /** * Called when the fling gesture is sent. */ void onFlingStartGesture(int vx, int vy); /** * Called when the fling cancel gesture is sent. */ void onFlingCancelGesture(); /** * Called when a fling event was not handled by the renderer. */ void onUnhandledFlingStartEvent(); /** * Called to indicate that a scroll update gesture had been consumed by the page. * This callback is called whenever any layer is scrolled (like a frame or div). It is * not called when a JS touch handler consumes the event (preventDefault), it is not called * for JS-initiated scrolling. */ void onScrollUpdateGestureConsumed(); } /** * An interface for controlling visibility and state of embedder-provided zoom controls. */ public interface ZoomControlsDelegate { /** * Called when it's reasonable to show zoom controls. */ void invokeZoomPicker(); /** * Called when zoom controls need to be hidden (e.g. when the view hides). */ void dismissZoomPicker(); /** * Called when page scale has been changed, so the controls can update their state. */ void updateZoomControls(); } /** * An interface that allows the embedder to be notified of changes to the parameters of the * currently displayed contents. * These notifications are consistent with respect to the UI thread (the size is the size of * the contents currently displayed on screen). */ public interface UpdateFrameInfoListener { /** * Called each time any of the parameters are changed. * * @param pageScaleFactor The page scale. */ void onFrameInfoUpdated(float pageScaleFactor); } private VSyncManager.Provider mVSyncProvider; private VSyncManager.Listener mVSyncListener; private int mVSyncSubscriberCount; private boolean mVSyncListenerRegistered; // To avoid IPC delay we use input events to directly trigger a vsync signal in the renderer. // When we do this, we also need to avoid sending the real vsync signal for the current // frame to avoid double-ticking. This flag is used to inhibit the next vsync notification. private boolean mDidSignalVSyncUsingInputEvent; public VSyncManager.Listener getVSyncListener(VSyncManager.Provider vsyncProvider) { if (mVSyncProvider != null && mVSyncListenerRegistered) { mVSyncProvider.unregisterVSyncListener(mVSyncListener); mVSyncListenerRegistered = false; } mVSyncProvider = vsyncProvider; mVSyncListener = new VSyncManager.Listener() { @Override public void updateVSync(long tickTimeMicros, long intervalMicros) { if (mNativeContentViewCore != 0) { nativeUpdateVSyncParameters(mNativeContentViewCore, tickTimeMicros, intervalMicros); } } @Override public void onVSync(long frameTimeMicros) { animateIfNecessary(frameTimeMicros); if (mDidSignalVSyncUsingInputEvent) { TraceEvent.instant("ContentViewCore::onVSync ignored"); mDidSignalVSyncUsingInputEvent = false; return; } if (mNativeContentViewCore != 0) { nativeOnVSync(mNativeContentViewCore, frameTimeMicros); } } }; if (mVSyncSubscriberCount > 0) { // addVSyncSubscriber() is called before getVSyncListener. vsyncProvider.registerVSyncListener(mVSyncListener); mVSyncListenerRegistered = true; } return mVSyncListener; } @CalledByNative void addVSyncSubscriber() { if (!isVSyncNotificationEnabled()) { mDidSignalVSyncUsingInputEvent = false; } if (mVSyncProvider != null && !mVSyncListenerRegistered) { mVSyncProvider.registerVSyncListener(mVSyncListener); mVSyncListenerRegistered = true; } mVSyncSubscriberCount++; } @CalledByNative void removeVSyncSubscriber() { if (mVSyncProvider != null && mVSyncSubscriberCount == 1) { assert mVSyncListenerRegistered; mVSyncProvider.unregisterVSyncListener(mVSyncListener); mVSyncListenerRegistered = false; } mVSyncSubscriberCount--; assert mVSyncSubscriberCount >= 0; } @CalledByNative private void resetVSyncNotification() { while (isVSyncNotificationEnabled()) removeVSyncSubscriber(); mVSyncSubscriberCount = 0; mVSyncListenerRegistered = false; mNeedAnimate = false; } private boolean isVSyncNotificationEnabled() { return mVSyncProvider != null && mVSyncListenerRegistered; } @CalledByNative private void setNeedsAnimate() { if (!mNeedAnimate) { mNeedAnimate = true; addVSyncSubscriber(); } } private final Context mContext; private ViewGroup mContainerView; private InternalAccessDelegate mContainerViewInternals; private WebContentsObserverAndroid mWebContentsObserver; private ContentViewClient mContentViewClient; private ContentSettings mContentSettings; // Native pointer to C++ ContentViewCoreImpl object which will be set by nativeInit(). private int mNativeContentViewCore = 0; private boolean mAttachedToWindow = false; private ContentViewGestureHandler mContentViewGestureHandler; private GestureStateListener mGestureStateListener; private ZoomManager mZoomManager; private ZoomControlsDelegate mZoomControlsDelegate; private PopupZoomer mPopupZoomer; private Runnable mFakeMouseMoveRunnable = null; // Only valid when focused on a text / password field. private ImeAdapter mImeAdapter; private ImeAdapter.AdapterInputConnectionFactory mAdapterInputConnectionFactory; private AdapterInputConnection mInputConnection; private SelectionHandleController mSelectionHandleController; private InsertionHandleController mInsertionHandleController; private Runnable mDeferredHandleFadeInRunnable; private PositionObserver mPositionObserver; private PositionObserver.Listener mPositionListener; // Size of the viewport in physical pixels as set from onSizeChanged. private int mViewportWidthPix; private int mViewportHeightPix; private int mPhysicalBackingWidthPix; private int mPhysicalBackingHeightPix; private int mOverdrawBottomHeightPix; private int mViewportSizeOffsetWidthPix; private int mViewportSizeOffsetHeightPix; private int mLocationInWindowX; private int mLocationInWindowY; // Cached copy of all positions and scales as reported by the renderer. private final RenderCoordinates mRenderCoordinates; private final RenderCoordinates.NormalizedPoint mStartHandlePoint; private final RenderCoordinates.NormalizedPoint mEndHandlePoint; private final RenderCoordinates.NormalizedPoint mInsertionHandlePoint; // Tracks whether a selection is currently active. When applied to selected text, indicates // whether the last selected text is still highlighted. private boolean mHasSelection; private String mLastSelectedText; private boolean mSelectionEditable; private ActionMode mActionMode; private boolean mUnselectAllOnActionModeDismiss; // Delegate that will handle GET downloads, and be notified of completion of POST downloads. private ContentViewDownloadDelegate mDownloadDelegate; // The AccessibilityInjector that handles loading Accessibility scripts into the web page. private AccessibilityInjector mAccessibilityInjector; // Whether native accessibility, i.e. without any script injection, is allowed. private boolean mNativeAccessibilityAllowed; // Whether native accessibility, i.e. without any script injection, has been enabled. private boolean mNativeAccessibilityEnabled; // Handles native accessibility, i.e. without any script injection. private BrowserAccessibilityManager mBrowserAccessibilityManager; // System accessibility service. private final AccessibilityManager mAccessibilityManager; // Allows us to dynamically respond when the accessibility script injection flag changes. private ContentObserver mAccessibilityScriptInjectionObserver; // Temporary notification to tell onSizeChanged to focus a form element, // because the OSK was just brought up. private boolean mUnfocusOnNextSizeChanged = false; private final Rect mFocusPreOSKViewportRect = new Rect(); // Used to keep track of whether we should try to undo the last zoom-to-textfield operation. private boolean mScrolledAndZoomedFocusedEditableNode = false; // Whether we use hardware-accelerated drawing. private boolean mHardwareAccelerated = false; // Whether we received a new frame since consumePendingRendererFrame() was last called. private boolean mPendingRendererFrame = false; // Whether we should animate at the next vsync tick. private boolean mNeedAnimate = false; private ViewAndroid mViewAndroid; public static class UMAActionAfterDoubleTap { public static final int NAVIGATE_BACK = 0; public static final int NAVIGATE_STOP = 1; public static final int NO_ACTION = 2; public static final int COUNT = 3; } public static class UMASingleTapType { public static final int DELAYED_TAP = 0; public static final int UNDELAYED_TAP = 1; public static final int COUNT = 2; } /** * Constructs a new ContentViewCore. Embedders must call initialize() after constructing * a ContentViewCore and before using it. * * @param context The context used to create this. */ public ContentViewCore(Context context) { mContext = context; WeakContext.initializeWeakContext(context); HeapStatsLogger.init(mContext.getApplicationContext()); mAdapterInputConnectionFactory = new AdapterInputConnectionFactory(); mRenderCoordinates = new RenderCoordinates(); mRenderCoordinates.setDeviceScaleFactor( getContext().getResources().getDisplayMetrics().density); mStartHandlePoint = mRenderCoordinates.createNormalizedPoint(); mEndHandlePoint = mRenderCoordinates.createNormalizedPoint(); mInsertionHandlePoint = mRenderCoordinates.createNormalizedPoint(); mAccessibilityManager = (AccessibilityManager) getContext().getSystemService(Context.ACCESSIBILITY_SERVICE); } /** * @return The context used for creating this ContentViewCore. */ @CalledByNative public Context getContext() { return mContext; } /** * @return The ViewGroup that all view actions of this ContentViewCore should interact with. */ public ViewGroup getContainerView() { return mContainerView; } /** * Specifies how much smaller the WebKit layout size should be relative to the size of this * view. * @param offsetXPix The X amount in pixels to shrink the viewport by. * @param offsetYPix The Y amount in pixels to shrink the viewport by. */ public void setViewportSizeOffset(int offsetXPix, int offsetYPix) { if (offsetXPix != mViewportSizeOffsetWidthPix || offsetYPix != mViewportSizeOffsetHeightPix) { mViewportSizeOffsetWidthPix = offsetXPix; mViewportSizeOffsetHeightPix = offsetYPix; if (mNativeContentViewCore != 0) nativeWasResized(mNativeContentViewCore); } } /** * Returns a delegate that can be used to add and remove views from the ContainerView. * * NOTE: Use with care, as not all ContentViewCore users setup their ContainerView in the same * way. In particular, the Android WebView has limitations on what implementation details can * be provided via a child view, as they are visible in the API and could introduce * compatibility breaks with existing applications. If in doubt, contact the * android_webview/OWNERS * * @return A ViewAndroidDelegate that can be used to add and remove views. */ @VisibleForTesting public ViewAndroidDelegate getViewAndroidDelegate() { return new ViewAndroidDelegate() { @Override public View acquireAnchorView() { View anchorView = new View(getContext()); mContainerView.addView(anchorView); return anchorView; } @Override @SuppressWarnings("deprecation") // AbsoluteLayout.LayoutParams public void setAnchorViewPosition( View view, float x, float y, float width, float height) { assert(view.getParent() == mContainerView); float scale = (float) DeviceDisplayInfo.create(getContext()).getDIPScale(); // The anchor view should not go outside the bounds of the ContainerView. int leftMargin = Math.round(x * scale); int topMargin = Math.round(mRenderCoordinates.getContentOffsetYPix() + y * scale); int scaledWidth = Math.round(width * scale); // ContentViewCore currently only supports these two container view types. if (mContainerView instanceof FrameLayout) { if (scaledWidth + leftMargin > mContainerView.getWidth()) { scaledWidth = mContainerView.getWidth() - leftMargin; } FrameLayout.LayoutParams lp = new FrameLayout.LayoutParams( scaledWidth, Math.round(height * scale)); lp.leftMargin = leftMargin; lp.topMargin = topMargin; view.setLayoutParams(lp); } else if (mContainerView instanceof AbsoluteLayout) { // This fixes the offset due to a difference in // scrolling model of WebView vs. Chrome. // TODO(sgurun) fix this to use mContainerView.getScroll[X/Y]() // as it naturally accounts for scroll differences between // these models. leftMargin += mRenderCoordinates.getScrollXPixInt(); topMargin += mRenderCoordinates.getScrollYPixInt(); android.widget.AbsoluteLayout.LayoutParams lp = new android.widget.AbsoluteLayout.LayoutParams( scaledWidth, (int)(height * scale), leftMargin, topMargin); view.setLayoutParams(lp); } else { Log.e(TAG, "Unknown layout " + mContainerView.getClass().getName()); } } @Override public void releaseAnchorView(View anchorView) { mContainerView.removeView(anchorView); } }; } @VisibleForTesting public ImeAdapter getImeAdapterForTest() { return mImeAdapter; } @VisibleForTesting public void setAdapterInputConnectionFactory(AdapterInputConnectionFactory factory) { mAdapterInputConnectionFactory = factory; } @VisibleForTesting public AdapterInputConnection getInputConnectionForTest() { return mInputConnection; } private ImeAdapter createImeAdapter(Context context) { return new ImeAdapter(new InputMethodManagerWrapper(context), new ImeAdapter.ImeAdapterDelegate() { @Override public void onImeEvent(boolean isFinish) { getContentViewClient().onImeEvent(); if (!isFinish) { hideHandles(); undoScrollFocusedEditableNodeIntoViewIfNeeded(false); } } @Override public void onSetFieldValue() { scrollFocusedEditableNodeIntoView(); } @Override public void onDismissInput() { getContentViewClient().onImeStateChangeRequested(false); } @Override public View getAttachedView() { return mContainerView; } @Override public ResultReceiver getNewShowKeyboardReceiver() { return new ResultReceiver(new Handler()) { @Override public void onReceiveResult(int resultCode, Bundle resultData) { getContentViewClient().onImeStateChangeRequested( resultCode == InputMethodManager.RESULT_SHOWN || resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN); if (resultCode == InputMethodManager.RESULT_SHOWN) { // If OSK is newly shown, delay the form focus until // the onSizeChanged (in order to adjust relative to the // new size). // TODO(jdduke): We should not assume that onSizeChanged will // always be called, crbug.com/294908. getContainerView().getWindowVisibleDisplayFrame( mFocusPreOSKViewportRect); } else if (resultCode == InputMethodManager.RESULT_UNCHANGED_SHOWN) { // If the OSK was already there, focus the form immediately. scrollFocusedEditableNodeIntoView(); } else { undoScrollFocusedEditableNodeIntoViewIfNeeded(false); } } }; } } ); } /** * Returns true if the given Activity has hardware acceleration enabled * in its manifest, or in its foreground window. * * TODO(husky): Remove when initialize() is refactored (see TODO there) * TODO(dtrainor) This is still used by other classes. Make sure to pull some version of this * out before removing it. */ public static boolean hasHardwareAcceleration(Activity activity) { // Has HW acceleration been enabled manually in the current window? Window window = activity.getWindow(); if (window != null) { if ((window.getAttributes().flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0) { return true; } } // Has HW acceleration been enabled in the manifest? try { ActivityInfo info = activity.getPackageManager().getActivityInfo( activity.getComponentName(), 0); if ((info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0) { return true; } } catch (PackageManager.NameNotFoundException e) { Log.e("Chrome", "getActivityInfo(self) should not fail"); } return false; } /** * Returns true if the given Context is a HW-accelerated Activity. * * TODO(husky): Remove when initialize() is refactored (see TODO there) */ private static boolean hasHardwareAcceleration(Context context) { if (context instanceof Activity) { return hasHardwareAcceleration((Activity) context); } return false; } /** * * @param containerView The view that will act as a container for all views created by this. * @param internalDispatcher Handles dispatching all hidden or super methods to the * containerView. * @param nativeWebContents A pointer to the native web contents. * @param windowAndroid An instance of the WindowAndroid. */ // Perform important post-construction set up of the ContentViewCore. // We do not require the containing view in the constructor to allow embedders to create a // ContentViewCore without having fully created its containing view. The containing view // is a vital component of the ContentViewCore, so embedders must exercise caution in what // they do with the ContentViewCore before calling initialize(). // We supply the nativeWebContents pointer here rather than in the constructor to allow us // to set the private browsing mode at a later point for the WebView implementation. // Note that the caller remains the owner of the nativeWebContents and is responsible for // deleting it after destroying the ContentViewCore. public void initialize(ViewGroup containerView, InternalAccessDelegate internalDispatcher, int nativeWebContents, WindowAndroid windowAndroid, int inputEventDeliveryMode) { // Check whether to use hardware acceleration. This is a bit hacky, and // only works if the Context is actually an Activity (as it is in the // Chrome application). // // What we're doing here is checking whether the app has *requested* // hardware acceleration by setting the appropriate flags. This does not // necessarily mean we're going to *get* hardware acceleration -- that's // up to the Android framework. // // TODO(husky): Once the native code has been updated so that the // HW acceleration flag can be set dynamically (Grace is doing this), // move this check into onAttachedToWindow(), where we can test for // HW support directly. mHardwareAccelerated = hasHardwareAcceleration(mContext); mContainerView = containerView; mPositionObserver = new ViewPositionObserver(mContainerView); mPositionListener = new PositionObserver.Listener() { @Override public void onPositionChanged(int x, int y) { if (isSelectionHandleShowing() || isInsertionHandleShowing()) { temporarilyHideTextHandles(); } } }; int windowNativePointer = windowAndroid != null ? windowAndroid.getNativePointer() : 0; int viewAndroidNativePointer = 0; if (windowNativePointer != 0) { mViewAndroid = new ViewAndroid(windowAndroid, getViewAndroidDelegate()); viewAndroidNativePointer = mViewAndroid.getNativePointer(); } mNativeContentViewCore = nativeInit(mHardwareAccelerated, nativeWebContents, viewAndroidNativePointer, windowNativePointer); mContentSettings = new ContentSettings(this, mNativeContentViewCore); initializeContainerView(internalDispatcher, inputEventDeliveryMode); mAccessibilityInjector = AccessibilityInjector.newInstance(this); String contentDescription = "Web View"; if (R.string.accessibility_content_view == 0) { Log.w(TAG, "Setting contentDescription to 'Web View' as no value was specified."); } else { contentDescription = mContext.getResources().getString( R.string.accessibility_content_view); } mContainerView.setContentDescription(contentDescription); mWebContentsObserver = new WebContentsObserverAndroid(this) { @Override public void didStartLoading(String url) { hidePopupDialog(); resetGestureDetectors(); } }; sendOrientationChangeEvent(); } @CalledByNative void onNativeContentViewCoreDestroyed(int nativeContentViewCore) { assert nativeContentViewCore == mNativeContentViewCore; mNativeContentViewCore = 0; } /** * Set the Container view Internals. * @param internalDispatcher Handles dispatching all hidden or super methods to the * containerView. */ public void setContainerViewInternals(InternalAccessDelegate internalDispatcher) { mContainerViewInternals = internalDispatcher; } /** * Initializes the View that will contain all Views created by the ContentViewCore. * * @param internalDispatcher Handles dispatching all hidden or super methods to the * containerView. */ private void initializeContainerView(InternalAccessDelegate internalDispatcher, int inputEventDeliveryMode) { TraceEvent.begin(); mContainerViewInternals = internalDispatcher; mContainerView.setWillNotDraw(false); mContainerView.setClickable(true); mZoomManager = new ZoomManager(mContext, this); mContentViewGestureHandler = new ContentViewGestureHandler(mContext, this, mZoomManager, inputEventDeliveryMode); mZoomControlsDelegate = new ZoomControlsDelegate() { @Override public void invokeZoomPicker() {} @Override public void dismissZoomPicker() {} @Override public void updateZoomControls() {} }; mRenderCoordinates.reset(); onRenderCoordinatesUpdated(); initPopupZoomer(mContext); mImeAdapter = createImeAdapter(mContext); TraceEvent.end(); } private void initPopupZoomer(Context context){ mPopupZoomer = new PopupZoomer(context); mPopupZoomer.setOnVisibilityChangedListener(new PopupZoomer.OnVisibilityChangedListener() { @Override public void onPopupZoomerShown(final PopupZoomer zoomer) { mContainerView.post(new Runnable() { @Override public void run() { if (mContainerView.indexOfChild(zoomer) == -1) { mContainerView.addView(zoomer); } else { assert false : "PopupZoomer should never be shown without being hidden"; } } }); } @Override public void onPopupZoomerHidden(final PopupZoomer zoomer) { mContainerView.post(new Runnable() { @Override public void run() { if (mContainerView.indexOfChild(zoomer) != -1) { mContainerView.removeView(zoomer); mContainerView.invalidate(); } else { assert false : "PopupZoomer should never be hidden without being shown"; } } }); } }); // TODO(yongsheng): LONG_TAP is not enabled in PopupZoomer. So need to dispatch a LONG_TAP // gesture if a user completes a tap on PopupZoomer UI after a LONG_PRESS gesture. PopupZoomer.OnTapListener listener = new PopupZoomer.OnTapListener() { @Override public boolean onSingleTap(View v, MotionEvent e) { mContainerView.requestFocus(); if (mNativeContentViewCore != 0) { nativeSingleTap(mNativeContentViewCore, e.getEventTime(), e.getX(), e.getY(), true); } return true; } @Override public boolean onLongPress(View v, MotionEvent e) { if (mNativeContentViewCore != 0) { nativeLongPress(mNativeContentViewCore, e.getEventTime(), e.getX(), e.getY(), true); } return true; } }; mPopupZoomer.setOnTapListener(listener); } /** * Destroy the internal state of the ContentView. This method may only be * called after the ContentView has been removed from the view system. No * other methods may be called on this ContentView after this method has * been called. */ public void destroy() { if (mNativeContentViewCore != 0) { nativeOnJavaContentViewCoreDestroyed(mNativeContentViewCore); } resetVSyncNotification(); mVSyncProvider = null; if (mViewAndroid != null) mViewAndroid.destroy(); mNativeContentViewCore = 0; mContentSettings = null; mJavaScriptInterfaces.clear(); mRetainedJavaScriptObjects.clear(); unregisterAccessibilityContentObserver(); } private void unregisterAccessibilityContentObserver() { if (mAccessibilityScriptInjectionObserver == null) { return; } getContext().getContentResolver().unregisterContentObserver( mAccessibilityScriptInjectionObserver); mAccessibilityScriptInjectionObserver = null; } /** * Returns true initially, false after destroy() has been called. * It is illegal to call any other public method after destroy(). */ public boolean isAlive() { return mNativeContentViewCore != 0; } /** * This is only useful for passing over JNI to native code that requires ContentViewCore*. * @return native ContentViewCore pointer. */ @CalledByNative public int getNativeContentViewCore() { return mNativeContentViewCore; } /** * For internal use. Throws IllegalStateException if mNativeContentView is 0. * Use this to ensure we get a useful Java stack trace, rather than a native * crash dump, from use-after-destroy bugs in Java code. */ void checkIsAlive() throws IllegalStateException { if (!isAlive()) { throw new IllegalStateException("ContentView used after destroy() was called"); } } public void setContentViewClient(ContentViewClient client) { if (client == null) { throw new IllegalArgumentException("The client can't be null."); } mContentViewClient = client; } ContentViewClient getContentViewClient() { if (mContentViewClient == null) { // We use the Null Object pattern to avoid having to perform a null check in this class. // We create it lazily because most of the time a client will be set almost immediately // after ContentView is created. mContentViewClient = new ContentViewClient(); // We don't set the native ContentViewClient pointer here on purpose. The native // implementation doesn't mind a null delegate and using one is better than passing a // Null Object, since we cut down on the number of JNI calls. } return mContentViewClient; } public int getBackgroundColor() { if (mNativeContentViewCore != 0) { return nativeGetBackgroundColor(mNativeContentViewCore); } return Color.WHITE; } @CalledByNative private void onBackgroundColorChanged(int color) { getContentViewClient().onBackgroundColorChanged(color); } /** * Load url without fixing up the url string. Consumers of ContentView are responsible for * ensuring the URL passed in is properly formatted (i.e. the scheme has been added if left * off during user input). * * @param params Parameters for this load. */ public void loadUrl(LoadUrlParams params) { if (mNativeContentViewCore == 0) return; nativeLoadUrl(mNativeContentViewCore, params.mUrl, params.mLoadUrlType, params.mTransitionType, params.mUaOverrideOption, params.getExtraHeadersString(), params.mPostData, params.mBaseUrlForDataUrl, params.mVirtualUrlForDataUrl, params.mCanLoadLocalResources); } /** * Stops loading the current web contents. */ public void stopLoading() { reportActionAfterDoubleTapUMA(ContentViewCore.UMAActionAfterDoubleTap.NAVIGATE_STOP); if (mNativeContentViewCore != 0) nativeStopLoading(mNativeContentViewCore); } /** * Get the URL of the current page. * * @return The URL of the current page. */ public String getUrl() { if (mNativeContentViewCore != 0) return nativeGetURL(mNativeContentViewCore); return null; } /** * Get the title of the current page. * * @return The title of the current page. */ public String getTitle() { if (mNativeContentViewCore != 0) return nativeGetTitle(mNativeContentViewCore); return null; } /** * Shows an interstitial page driven by the passed in delegate. * * @param url The URL being blocked by the interstitial. * @param delegate The delegate handling the interstitial. */ @VisibleForTesting public void showInterstitialPage( String url, InterstitialPageDelegateAndroid delegate) { if (mNativeContentViewCore == 0) return; nativeShowInterstitialPage(mNativeContentViewCore, url, delegate.getNative()); } /** * @return Whether the page is currently showing an interstitial, such as a bad HTTPS page. */ public boolean isShowingInterstitialPage() { return mNativeContentViewCore == 0 ? false : nativeIsShowingInterstitialPage(mNativeContentViewCore); } /** * Mark any new frames that have arrived since this function was last called as non-pending. * * @return Whether there was a pending frame from the renderer. */ public boolean consumePendingRendererFrame() { boolean hadPendingFrame = mPendingRendererFrame; mPendingRendererFrame = false; return hadPendingFrame; } /** * @return Viewport width in physical pixels as set from onSizeChanged. */ @CalledByNative public int getViewportWidthPix() { return mViewportWidthPix; } /** * @return Viewport height in physical pixels as set from onSizeChanged. */ @CalledByNative public int getViewportHeightPix() { return mViewportHeightPix; } /** * @return Width of underlying physical surface. */ @CalledByNative public int getPhysicalBackingWidthPix() { return mPhysicalBackingWidthPix; } /** * @return Height of underlying physical surface. */ @CalledByNative public int getPhysicalBackingHeightPix() { return mPhysicalBackingHeightPix; } /** * @return Amount the output surface extends past the bottom of the window viewport. */ @CalledByNative public int getOverdrawBottomHeightPix() { return mOverdrawBottomHeightPix; } /** * @return The amount to shrink the viewport relative to {@link #getViewportWidthPix()}. */ @CalledByNative public int getViewportSizeOffsetWidthPix() { return mViewportSizeOffsetWidthPix; } /** * @return The amount to shrink the viewport relative to {@link #getViewportHeightPix()}. */ @CalledByNative public int getViewportSizeOffsetHeightPix() { return mViewportSizeOffsetHeightPix; } /** * @see android.webkit.WebView#getContentHeight() */ public float getContentHeightCss() { return mRenderCoordinates.getContentHeightCss(); } /** * @see android.webkit.WebView#getContentWidth() */ public float getContentWidthCss() { return mRenderCoordinates.getContentWidthCss(); } public Bitmap getBitmap() { return getBitmap(getViewportWidthPix(), getViewportHeightPix()); } public Bitmap getBitmap(int width, int height) { if (width == 0 || height == 0 || getViewportWidthPix() == 0 || getViewportHeightPix() == 0) { return null; } Bitmap b = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); if (mNativeContentViewCore != 0 && nativePopulateBitmapFromCompositor(mNativeContentViewCore, b)) { // If we successfully grabbed a bitmap, check if we have to draw the Android overlay // components as well. if (mContainerView.getChildCount() > 0) { Canvas c = new Canvas(b); c.scale(width / (float) getViewportWidthPix(), height / (float) getViewportHeightPix()); mContainerView.draw(c); } return b; } return null; } /** * Generates a bitmap of the content that is performance optimized based on capture time. * * <p> * To have a consistent capture time across devices, we will scale down the captured bitmap * where necessary to reduce the time to generate the bitmap. * * @param width The width of the content to be captured. * @param height The height of the content to be captured. * @return A pair of the generated bitmap, and the scale that needs to be applied to return the * bitmap to it's original size (i.e. if the bitmap is scaled down 50%, this * will be 2). */ public Pair<Bitmap, Float> getScaledPerformanceOptimizedBitmap(int width, int height) { float scale = 1f; // On tablets, always scale down to MDPI for performance reasons. if (DeviceUtils.isTablet(getContext())) { scale = getContext().getResources().getDisplayMetrics().density; } return Pair.create( getBitmap((int) (width / scale), (int) (height / scale)), scale); } /** * @return Whether the current WebContents has a previous navigation entry. */ public boolean canGoBack() { return mNativeContentViewCore != 0 && nativeCanGoBack(mNativeContentViewCore); } /** * @return Whether the current WebContents has a navigation entry after the current one. */ public boolean canGoForward() { return mNativeContentViewCore != 0 && nativeCanGoForward(mNativeContentViewCore); } /** * @param offset The offset into the navigation history. * @return Whether we can move in history by given offset */ public boolean canGoToOffset(int offset) { return mNativeContentViewCore != 0 && nativeCanGoToOffset(mNativeContentViewCore, offset); } /** * Navigates to the specified offset from the "current entry". Does nothing if the offset is out * of bounds. * @param offset The offset into the navigation history. */ public void goToOffset(int offset) { if (mNativeContentViewCore != 0) nativeGoToOffset(mNativeContentViewCore, offset); } @Override public void goToNavigationIndex(int index) { if (mNativeContentViewCore != 0) nativeGoToNavigationIndex(mNativeContentViewCore, index); } /** * Goes to the navigation entry before the current one. */ public void goBack() { reportActionAfterDoubleTapUMA(ContentViewCore.UMAActionAfterDoubleTap.NAVIGATE_BACK); if (mNativeContentViewCore != 0) nativeGoBack(mNativeContentViewCore); } /** * Goes to the navigation entry following the current one. */ public void goForward() { if (mNativeContentViewCore != 0) nativeGoForward(mNativeContentViewCore); } /** * Loads the current navigation if there is a pending lazy load (after tab restore). */ public void loadIfNecessary() { if (mNativeContentViewCore != 0) nativeLoadIfNecessary(mNativeContentViewCore); } /** * Requests the current navigation to be loaded upon the next call to loadIfNecessary(). */ public void requestRestoreLoad() { if (mNativeContentViewCore != 0) nativeRequestRestoreLoad(mNativeContentViewCore); } /** * Reload the current page. */ public void reload(boolean checkForRepost) { mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary(); if (mNativeContentViewCore != 0) { nativeReload(mNativeContentViewCore, checkForRepost); } } /** * Reload the current page, ignoring the contents of the cache. */ public void reloadIgnoringCache(boolean checkForRepost) { mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary(); if (mNativeContentViewCore != 0) { nativeReloadIgnoringCache(mNativeContentViewCore, checkForRepost); } } /** * Cancel the pending reload. */ public void cancelPendingReload() { if (mNativeContentViewCore != 0) nativeCancelPendingReload(mNativeContentViewCore); } /** * Continue the pending reload. */ public void continuePendingReload() { if (mNativeContentViewCore != 0) nativeContinuePendingReload(mNativeContentViewCore); } /** * Clears the ContentViewCore's page history in both the backwards and * forwards directions. */ public void clearHistory() { if (mNativeContentViewCore != 0) nativeClearHistory(mNativeContentViewCore); } /** * @return The selected text (empty if no text selected). */ public String getSelectedText() { return mHasSelection ? mLastSelectedText : ""; } /** * @return Whether the current selection is editable (false if no text selected). */ public boolean isSelectionEditable() { return mHasSelection ? mSelectionEditable : false; } // End FrameLayout overrides. /** * @see View#onTouchEvent(MotionEvent) */ public boolean onTouchEvent(MotionEvent event) { undoScrollFocusedEditableNodeIntoViewIfNeeded(false); return mContentViewGestureHandler.onTouchEvent(event); } /** * @return ContentViewGestureHandler for all MotionEvent and gesture related calls. */ ContentViewGestureHandler getContentViewGestureHandler() { return mContentViewGestureHandler; } @Override public boolean sendTouchEvent(long timeMs, int action, TouchPoint[] pts) { if (mNativeContentViewCore != 0) { return nativeSendTouchEvent(mNativeContentViewCore, timeMs, action, pts); } return false; } @SuppressWarnings("unused") @CalledByNative private void hasTouchEventHandlers(boolean hasTouchHandlers) { mContentViewGestureHandler.hasTouchEventHandlers(hasTouchHandlers); } @SuppressWarnings("unused") @CalledByNative private void confirmTouchEvent(int ackResult) { mContentViewGestureHandler.confirmTouchEvent(ackResult); } @SuppressWarnings("unused") @CalledByNative private void unhandledFlingStartEvent() { if (mGestureStateListener != null) { mGestureStateListener.onUnhandledFlingStartEvent(); } } @SuppressWarnings("unused") @CalledByNative private void onScrollUpdateGestureConsumed() { if (mGestureStateListener != null) { mGestureStateListener.onScrollUpdateGestureConsumed(); } } private void reportActionAfterDoubleTapUMA(int type) { mContentViewGestureHandler.reportActionAfterDoubleTapUMA(type); } @Override public boolean sendGesture(int type, long timeMs, int x, int y, Bundle b) { if (offerGestureToEmbedder(type)) return false; if (mNativeContentViewCore == 0) return false; updateTextHandlesForGesture(type); updateGestureStateListener(type, b); switch (type) { case ContentViewGestureHandler.GESTURE_SHOW_PRESSED_STATE: nativeShowPressState(mNativeContentViewCore, timeMs, x, y); return true; case ContentViewGestureHandler.GESTURE_TAP_CANCEL: nativeTapCancel(mNativeContentViewCore, timeMs, x, y); return true; case ContentViewGestureHandler.GESTURE_TAP_DOWN: nativeTapDown(mNativeContentViewCore, timeMs, x, y); return true; case ContentViewGestureHandler.GESTURE_DOUBLE_TAP: nativeDoubleTap(mNativeContentViewCore, timeMs, x, y); return true; case ContentViewGestureHandler.GESTURE_SINGLE_TAP_UP: nativeSingleTap(mNativeContentViewCore, timeMs, x, y, false); return true; case ContentViewGestureHandler.GESTURE_SINGLE_TAP_CONFIRMED: handleTapOrPress(timeMs, x, y, 0, b.getBoolean(ContentViewGestureHandler.SHOW_PRESS, false)); return true; case ContentViewGestureHandler.GESTURE_SINGLE_TAP_UNCONFIRMED: nativeSingleTapUnconfirmed(mNativeContentViewCore, timeMs, x, y); return true; case ContentViewGestureHandler.GESTURE_LONG_PRESS: handleTapOrPress(timeMs, x, y, IS_LONG_PRESS, false); return true; case ContentViewGestureHandler.GESTURE_LONG_TAP: handleTapOrPress(timeMs, x, y, IS_LONG_TAP, false); return true; case ContentViewGestureHandler.GESTURE_SCROLL_START: nativeScrollBegin(mNativeContentViewCore, timeMs, x, y); return true; case ContentViewGestureHandler.GESTURE_SCROLL_BY: { int dx = b.getInt(ContentViewGestureHandler.DISTANCE_X); int dy = b.getInt(ContentViewGestureHandler.DISTANCE_Y); nativeScrollBy(mNativeContentViewCore, timeMs, x, y, dx, dy); return true; } case ContentViewGestureHandler.GESTURE_SCROLL_END: nativeScrollEnd(mNativeContentViewCore, timeMs); return true; case ContentViewGestureHandler.GESTURE_FLING_START: nativeFlingStart(mNativeContentViewCore, timeMs, x, y, b.getInt(ContentViewGestureHandler.VELOCITY_X, 0), b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0)); return true; case ContentViewGestureHandler.GESTURE_FLING_CANCEL: nativeFlingCancel(mNativeContentViewCore, timeMs); return true; case ContentViewGestureHandler.GESTURE_PINCH_BEGIN: nativePinchBegin(mNativeContentViewCore, timeMs, x, y); return true; case ContentViewGestureHandler.GESTURE_PINCH_BY: nativePinchBy(mNativeContentViewCore, timeMs, x, y, b.getFloat(ContentViewGestureHandler.DELTA, 0)); return true; case ContentViewGestureHandler.GESTURE_PINCH_END: nativePinchEnd(mNativeContentViewCore, timeMs); return true; default: return false; } } @Override public void sendSingleTapUMA(int type) { if (mNativeContentViewCore == 0) return; nativeSendSingleTapUma( mNativeContentViewCore, type, UMASingleTapType.COUNT); } @Override public void sendActionAfterDoubleTapUMA(int type, boolean clickDelayEnabled) { if (mNativeContentViewCore == 0) return; nativeSendActionAfterDoubleTapUma( mNativeContentViewCore, type, clickDelayEnabled, UMAActionAfterDoubleTap.COUNT); } @Override public void onSentLastGestureForVSync(long eventTimeMs) { if (isVSyncNotificationEnabled()) { mDidSignalVSyncUsingInputEvent = true; } if (mNativeContentViewCore != 0) { nativeOnVSync(mNativeContentViewCore, eventTimeMs * 1000); } } public void setGestureStateListener(GestureStateListener pinchGestureStateListener) { mGestureStateListener = pinchGestureStateListener; } void updateGestureStateListener(int gestureType, Bundle b) { if (mGestureStateListener == null) return; switch (gestureType) { case ContentViewGestureHandler.GESTURE_PINCH_BEGIN: mGestureStateListener.onPinchGestureStart(); break; case ContentViewGestureHandler.GESTURE_PINCH_END: mGestureStateListener.onPinchGestureEnd(); break; case ContentViewGestureHandler.GESTURE_FLING_START: mGestureStateListener.onFlingStartGesture( b.getInt(ContentViewGestureHandler.VELOCITY_X, 0), b.getInt(ContentViewGestureHandler.VELOCITY_Y, 0)); break; case ContentViewGestureHandler.GESTURE_FLING_CANCEL: mGestureStateListener.onFlingCancelGesture(); break; default: break; } } public interface JavaScriptCallback { void handleJavaScriptResult(String jsonResult); } /** * Injects the passed Javascript code in the current page and evaluates it. * If a result is required, pass in a callback. * Used in automation tests. * * @param script The Javascript to execute. * @param callback The callback to be fired off when a result is ready. The script's * result will be json encoded and passed as the parameter, and the call * will be made on the main thread. * If no result is required, pass null. */ public void evaluateJavaScript(String script, JavaScriptCallback callback) { if (mNativeContentViewCore == 0) return; nativeEvaluateJavaScript(mNativeContentViewCore, script, callback, false); } /** * Injects the passed Javascript code in the current page and evaluates it. * If there is no page existing, a new one will be created. * * @param script The Javascript to execute. */ public void evaluateJavaScriptEvenIfNotYetNavigated(String script) { if (mNativeContentViewCore == 0) return; nativeEvaluateJavaScript(mNativeContentViewCore, script, null, true); } /** * To be called when the ContentView is shown. */ public void onShow() { nativeOnShow(mNativeContentViewCore); setAccessibilityState(mAccessibilityManager.isEnabled()); } /** * To be called when the ContentView is hidden. */ public void onHide() { hidePopupDialog(); setInjectedAccessibility(false); nativeOnHide(mNativeContentViewCore); } /** * Return the ContentSettings object used to retrieve the settings for this * ContentViewCore. For modifications, ChromeNativePreferences is to be used. * @return A ContentSettings object that can be used to retrieve this * ContentViewCore's settings. */ public ContentSettings getContentSettings() { return mContentSettings; } @Override public boolean didUIStealScroll(float x, float y) { return getContentViewClient().shouldOverrideScroll( x, y, computeHorizontalScrollOffset(), computeVerticalScrollOffset()); } private void onRenderCoordinatesUpdated() { if (mContentViewGestureHandler == null) return; // We disable double tap zoom for pages that have a width=device-width // or narrower viewport (indicating that this is a mobile-optimized or // responsive web design, so text will be legible without zooming). // We also disable it for pages that disallow the user from zooming in // or out (even if they don't have a device-width or narrower viewport). mContentViewGestureHandler.updateShouldDisableDoubleTap( mRenderCoordinates.hasMobileViewport() || mRenderCoordinates.hasFixedPageScale()); } private void hidePopupDialog() { SelectPopupDialog.hide(this); hideHandles(); hideSelectActionBar(); } void hideSelectActionBar() { if (mActionMode != null) { mActionMode.finish(); mActionMode = null; } } public boolean isSelectActionBarShowing() { return mActionMode != null; } private void resetGestureDetectors() { mContentViewGestureHandler.resetGestureHandlers(); } /** * @see View#onAttachedToWindow() */ @SuppressWarnings("javadoc") public void onAttachedToWindow() { mAttachedToWindow = true; if (mNativeContentViewCore != 0) { int pid = nativeGetCurrentRenderProcessId(mNativeContentViewCore); ChildProcessLauncher.getBindingManager().bindAsHighPriority(pid); // Normally the initial binding is removed in onRenderProcessSwap(), but it is possible // to construct WebContents and spawn the renderer before passing it to ContentViewCore. // In this case there will be no onRenderProcessSwap() call and the initial binding will // be removed here. ChildProcessLauncher.getBindingManager().removeInitialBinding(pid); } setAccessibilityState(mAccessibilityManager.isEnabled()); } /** * @see View#onDetachedFromWindow() */ @SuppressWarnings("javadoc") public void onDetachedFromWindow() { mAttachedToWindow = false; if (mNativeContentViewCore != 0) { int pid = nativeGetCurrentRenderProcessId(mNativeContentViewCore); ChildProcessLauncher.getBindingManager().unbindAsHighPriority(pid); } setInjectedAccessibility(false); hidePopupDialog(); mZoomControlsDelegate.dismissZoomPicker(); unregisterAccessibilityContentObserver(); } /** * @see View#onVisibilityChanged(android.view.View, int) */ public void onVisibilityChanged(View changedView, int visibility) { if (visibility != View.VISIBLE) { mZoomControlsDelegate.dismissZoomPicker(); } } /** * @see View#onCreateInputConnection(EditorInfo) */ public InputConnection onCreateInputConnection(EditorInfo outAttrs) { if (!mImeAdapter.hasTextInputType()) { // Although onCheckIsTextEditor will return false in this case, the EditorInfo // is still used by the InputMethodService. Need to make sure the IME doesn't // enter fullscreen mode. outAttrs.imeOptions = EditorInfo.IME_FLAG_NO_FULLSCREEN; } mInputConnection = mAdapterInputConnectionFactory.get(mContainerView, mImeAdapter, outAttrs); return mInputConnection; } public Editable getEditableForTest() { return mInputConnection.getEditable(); } /** * @see View#onCheckIsTextEditor() */ public boolean onCheckIsTextEditor() { return mImeAdapter.hasTextInputType(); } /** * @see View#onConfigurationChanged(Configuration) */ @SuppressWarnings("javadoc") public void onConfigurationChanged(Configuration newConfig) { TraceEvent.begin(); if (newConfig.keyboard != Configuration.KEYBOARD_NOKEYS) { mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore), ImeAdapter.getTextInputTypeNone(), AdapterInputConnection.INVALID_SELECTION, AdapterInputConnection.INVALID_SELECTION); InputMethodManager manager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); manager.restartInput(mContainerView); } mContainerViewInternals.super_onConfigurationChanged(newConfig); // Make sure the size is up to date in JavaScript's window.onorientationchanged. mContainerView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() { @Override public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) { mContainerView.removeOnLayoutChangeListener(this); sendOrientationChangeEvent(); } }); // To request layout has side effect, but it seems OK as it only happen in // onConfigurationChange and layout has to be changed in most case. mContainerView.requestLayout(); TraceEvent.end(); } /** * @see View#onSizeChanged(int, int, int, int) */ @SuppressWarnings("javadoc") public void onSizeChanged(int wPix, int hPix, int owPix, int ohPix) { if (getViewportWidthPix() == wPix && getViewportHeightPix() == hPix) return; mViewportWidthPix = wPix; mViewportHeightPix = hPix; if (mNativeContentViewCore != 0) { nativeWasResized(mNativeContentViewCore); } updateAfterSizeChanged(); } /** * Called when the ContentView's position in the activity window changed. This information is * used for cropping screenshots. */ public void onLocationInWindowChanged(int x, int y) { mLocationInWindowX = x; mLocationInWindowY = y; } /** * Called when the underlying surface the compositor draws to changes size. * This may be larger than the viewport size. */ public void onPhysicalBackingSizeChanged(int wPix, int hPix) { if (mPhysicalBackingWidthPix == wPix && mPhysicalBackingHeightPix == hPix) return; mPhysicalBackingWidthPix = wPix; mPhysicalBackingHeightPix = hPix; if (mNativeContentViewCore != 0) { nativeWasResized(mNativeContentViewCore); } } /** * Called when the amount the surface is overdrawing off the bottom has changed. * @param overdrawHeightPix The overdraw height. */ public void onOverdrawBottomHeightChanged(int overdrawHeightPix) { if (mOverdrawBottomHeightPix == overdrawHeightPix) return; mOverdrawBottomHeightPix = overdrawHeightPix; if (mNativeContentViewCore != 0) { nativeWasResized(mNativeContentViewCore); } } private void updateAfterSizeChanged() { mPopupZoomer.hide(false); // Execute a delayed form focus operation because the OSK was brought // up earlier. if (!mFocusPreOSKViewportRect.isEmpty()) { Rect rect = new Rect(); getContainerView().getWindowVisibleDisplayFrame(rect); if (!rect.equals(mFocusPreOSKViewportRect)) { // Only assume the OSK triggered the onSizeChanged if width was preserved. if (rect.width() == mFocusPreOSKViewportRect.width()) { scrollFocusedEditableNodeIntoView(); } mFocusPreOSKViewportRect.setEmpty(); } } else if (mUnfocusOnNextSizeChanged) { undoScrollFocusedEditableNodeIntoViewIfNeeded(true); mUnfocusOnNextSizeChanged = false; } } private void scrollFocusedEditableNodeIntoView() { if (mNativeContentViewCore != 0) { Runnable scrollTask = new Runnable() { @Override public void run() { if (mNativeContentViewCore != 0) { nativeScrollFocusedEditableNodeIntoView(mNativeContentViewCore); } } }; scrollTask.run(); // The native side keeps track of whether the zoom and scroll actually occurred. It is // more efficient to do it this way and sometimes fire an unnecessary message rather // than synchronize with the renderer and always have an additional message. mScrolledAndZoomedFocusedEditableNode = true; } } private void undoScrollFocusedEditableNodeIntoViewIfNeeded(boolean backButtonPressed) { // The only call to this function that matters is the first call after the // scrollFocusedEditableNodeIntoView function call. // If the first call to this function is a result of a back button press we want to undo the // preceding scroll. If the call is a result of some other action we don't want to perform // an undo. // All subsequent calls are ignored since only the scroll function sets // mScrolledAndZoomedFocusedEditableNode to true. if (mScrolledAndZoomedFocusedEditableNode && backButtonPressed && mNativeContentViewCore != 0) { Runnable scrollTask = new Runnable() { @Override public void run() { if (mNativeContentViewCore != 0) { nativeUndoScrollFocusedEditableNodeIntoView(mNativeContentViewCore); } } }; scrollTask.run(); } mScrolledAndZoomedFocusedEditableNode = false; } /** * @see View#onWindowFocusChanged(boolean) */ public void onWindowFocusChanged(boolean hasWindowFocus) { if (!hasWindowFocus) { mContentViewGestureHandler.onWindowFocusLost(); } } public void onFocusChanged(boolean gainFocus) { if (!gainFocus) getContentViewClient().onImeStateChangeRequested(false); if (mNativeContentViewCore != 0) nativeSetFocus(mNativeContentViewCore, gainFocus); } /** * @see View#onKeyUp(int, KeyEvent) */ public boolean onKeyUp(int keyCode, KeyEvent event) { if (mPopupZoomer.isShowing() && keyCode == KeyEvent.KEYCODE_BACK) { mPopupZoomer.hide(true); return true; } return mContainerViewInternals.super_onKeyUp(keyCode, event); } /** * @see View#dispatchKeyEventPreIme(KeyEvent) */ public boolean dispatchKeyEventPreIme(KeyEvent event) { try { TraceEvent.begin(); if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && mImeAdapter.isActive()) { mUnfocusOnNextSizeChanged = true; } else { undoScrollFocusedEditableNodeIntoViewIfNeeded(false); } return mContainerViewInternals.super_dispatchKeyEventPreIme(event); } finally { TraceEvent.end(); } } /** * @see View#dispatchKeyEvent(KeyEvent) */ public boolean dispatchKeyEvent(KeyEvent event) { if (getContentViewClient().shouldOverrideKeyEvent(event)) { return mContainerViewInternals.super_dispatchKeyEvent(event); } if (mImeAdapter.dispatchKeyEvent(event)) return true; return mContainerViewInternals.super_dispatchKeyEvent(event); } /** * @see View#onHoverEvent(MotionEvent) * Mouse move events are sent on hover enter, hover move and hover exit. * They are sent on hover exit because sometimes it acts as both a hover * move and hover exit. */ public boolean onHoverEvent(MotionEvent event) { TraceEvent.begin("onHoverEvent"); mContainerView.removeCallbacks(mFakeMouseMoveRunnable); if (mBrowserAccessibilityManager != null) { return mBrowserAccessibilityManager.onHoverEvent(event); } if (mNativeContentViewCore != 0) { nativeSendMouseMoveEvent(mNativeContentViewCore, event.getEventTime(), event.getX(), event.getY()); } TraceEvent.end("onHoverEvent"); return true; } /** * @see View#onGenericMotionEvent(MotionEvent) */ public boolean onGenericMotionEvent(MotionEvent event) { if ((event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { switch (event.getAction()) { case MotionEvent.ACTION_SCROLL: nativeSendMouseWheelEvent(mNativeContentViewCore, event.getEventTime(), event.getX(), event.getY(), event.getAxisValue(MotionEvent.AXIS_VSCROLL)); mContainerView.removeCallbacks(mFakeMouseMoveRunnable); // Send a delayed onMouseMove event so that we end // up hovering over the right position after the scroll. final MotionEvent eventFakeMouseMove = MotionEvent.obtain(event); mFakeMouseMoveRunnable = new Runnable() { @Override public void run() { onHoverEvent(eventFakeMouseMove); } }; mContainerView.postDelayed(mFakeMouseMoveRunnable, 250); return true; } } return mContainerViewInternals.super_onGenericMotionEvent(event); } /** * @see View#scrollBy(int, int) * Currently the ContentView scrolling happens in the native side. In * the Java view system, it is always pinned at (0, 0). scrollBy() and scrollTo() * are overridden, so that View's mScrollX and mScrollY will be unchanged at * (0, 0). This is critical for drawing ContentView correctly. */ public void scrollBy(int xPix, int yPix) { if (mNativeContentViewCore != 0) { nativeScrollBy(mNativeContentViewCore, System.currentTimeMillis(), 0, 0, xPix, yPix); } } /** * @see View#scrollTo(int, int) */ public void scrollTo(int xPix, int yPix) { if (mNativeContentViewCore == 0) return; final float xCurrentPix = mRenderCoordinates.getScrollXPix(); final float yCurrentPix = mRenderCoordinates.getScrollYPix(); final float dxPix = xPix - xCurrentPix; final float dyPix = yPix - yCurrentPix; if (dxPix != 0 || dyPix != 0) { long time = System.currentTimeMillis(); nativeScrollBegin(mNativeContentViewCore, time, xCurrentPix, yCurrentPix); nativeScrollBy(mNativeContentViewCore, time, xCurrentPix, yCurrentPix, dxPix, dyPix); nativeScrollEnd(mNativeContentViewCore, time); } } // NOTE: this can go away once ContentView.getScrollX() reports correct values. // see: b/6029133 public int getNativeScrollXForTest() { return mRenderCoordinates.getScrollXPixInt(); } // NOTE: this can go away once ContentView.getScrollY() reports correct values. // see: b/6029133 public int getNativeScrollYForTest() { return mRenderCoordinates.getScrollYPixInt(); } /** * @see View#computeHorizontalScrollExtent() */ @SuppressWarnings("javadoc") public int computeHorizontalScrollExtent() { return mRenderCoordinates.getLastFrameViewportWidthPixInt(); } /** * @see View#computeHorizontalScrollOffset() */ @SuppressWarnings("javadoc") public int computeHorizontalScrollOffset() { return mRenderCoordinates.getScrollXPixInt(); } /** * @see View#computeHorizontalScrollRange() */ @SuppressWarnings("javadoc") public int computeHorizontalScrollRange() { return mRenderCoordinates.getContentWidthPixInt(); } /** * @see View#computeVerticalScrollExtent() */ @SuppressWarnings("javadoc") public int computeVerticalScrollExtent() { return mRenderCoordinates.getLastFrameViewportHeightPixInt(); } /** * @see View#computeVerticalScrollOffset() */ @SuppressWarnings("javadoc") public int computeVerticalScrollOffset() { return mRenderCoordinates.getScrollYPixInt(); } /** * @see View#computeVerticalScrollRange() */ @SuppressWarnings("javadoc") public int computeVerticalScrollRange() { return mRenderCoordinates.getContentHeightPixInt(); } // End FrameLayout overrides. /** * @see View#awakenScrollBars(int, boolean) */ @SuppressWarnings("javadoc") public boolean awakenScrollBars(int startDelay, boolean invalidate) { // For the default implementation of ContentView which draws the scrollBars on the native // side, calling this function may get us into a bad state where we keep drawing the // scrollBars, so disable it by always returning false. if (mContainerView.getScrollBarStyle() == View.SCROLLBARS_INSIDE_OVERLAY) { return false; } else { return mContainerViewInternals.super_awakenScrollBars(startDelay, invalidate); } } private void handleTapOrPress( long timeMs, float xPix, float yPix, int isLongPressOrTap, boolean showPress) { if (mContainerView.isFocusable() && mContainerView.isFocusableInTouchMode() && !mContainerView.isFocused()) { mContainerView.requestFocus(); } if (!mPopupZoomer.isShowing()) mPopupZoomer.setLastTouch(xPix, yPix); if (isLongPressOrTap == IS_LONG_PRESS) { getInsertionHandleController().allowAutomaticShowing(); getSelectionHandleController().allowAutomaticShowing(); if (mNativeContentViewCore != 0) { nativeLongPress(mNativeContentViewCore, timeMs, xPix, yPix, false); } } else if (isLongPressOrTap == IS_LONG_TAP) { getInsertionHandleController().allowAutomaticShowing(); getSelectionHandleController().allowAutomaticShowing(); if (mNativeContentViewCore != 0) { nativeLongTap(mNativeContentViewCore, timeMs, xPix, yPix, false); } } else { if (!showPress && mNativeContentViewCore != 0) { nativeShowPressState(mNativeContentViewCore, timeMs, xPix, yPix); } if (mSelectionEditable) getInsertionHandleController().allowAutomaticShowing(); if (mNativeContentViewCore != 0) { nativeSingleTap(mNativeContentViewCore, timeMs, xPix, yPix, false); } } } public void setZoomControlsDelegate(ZoomControlsDelegate zoomControlsDelegate) { mZoomControlsDelegate = zoomControlsDelegate; } public void updateMultiTouchZoomSupport(boolean supportsMultiTouchZoom) { mZoomManager.updateMultiTouchSupport(supportsMultiTouchZoom); } public void updateDoubleTapSupport(boolean supportsDoubleTap) { mContentViewGestureHandler.updateDoubleTapSupport(supportsDoubleTap); } public void selectPopupMenuItems(int[] indices) { if (mNativeContentViewCore != 0) { nativeSelectPopupMenuItems(mNativeContentViewCore, indices); } } /** * Get the screen orientation from the OS and push it to WebKit. * * TODO(husky): Add a hook for mock orientations. */ private void sendOrientationChangeEvent() { if (mNativeContentViewCore == 0) return; WindowManager windowManager = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE); switch (windowManager.getDefaultDisplay().getRotation()) { case Surface.ROTATION_90: nativeSendOrientationChangeEvent(mNativeContentViewCore, 90); break; case Surface.ROTATION_180: nativeSendOrientationChangeEvent(mNativeContentViewCore, 180); break; case Surface.ROTATION_270: nativeSendOrientationChangeEvent(mNativeContentViewCore, -90); break; case Surface.ROTATION_0: nativeSendOrientationChangeEvent(mNativeContentViewCore, 0); break; default: Log.w(TAG, "Unknown rotation!"); break; } } /** * Register the delegate to be used when content can not be handled by * the rendering engine, and should be downloaded instead. This will replace * the current delegate, if any. * @param delegate An implementation of ContentViewDownloadDelegate. */ public void setDownloadDelegate(ContentViewDownloadDelegate delegate) { mDownloadDelegate = delegate; } // Called by DownloadController. ContentViewDownloadDelegate getDownloadDelegate() { return mDownloadDelegate; } private SelectionHandleController getSelectionHandleController() { if (mSelectionHandleController == null) { mSelectionHandleController = new SelectionHandleController( getContainerView(), mPositionObserver) { @Override public void selectBetweenCoordinates(int x1, int y1, int x2, int y2) { if (mNativeContentViewCore != 0 && !(x1 == x2 && y1 == y2)) { nativeSelectBetweenCoordinates(mNativeContentViewCore, x1, y1 - mRenderCoordinates.getContentOffsetYPix(), x2, y2 - mRenderCoordinates.getContentOffsetYPix()); } } @Override public void showHandles(int startDir, int endDir) { super.showHandles(startDir, endDir); showSelectActionBar(); } }; mSelectionHandleController.hideAndDisallowAutomaticShowing(); } return mSelectionHandleController; } private InsertionHandleController getInsertionHandleController() { if (mInsertionHandleController == null) { mInsertionHandleController = new InsertionHandleController( getContainerView(), mPositionObserver) { private static final int AVERAGE_LINE_HEIGHT = 14; @Override public void setCursorPosition(int x, int y) { if (mNativeContentViewCore != 0) { nativeMoveCaret(mNativeContentViewCore, x, y - mRenderCoordinates.getContentOffsetYPix()); } } @Override public void paste() { mImeAdapter.paste(); hideHandles(); } @Override public int getLineHeight() { return (int) Math.ceil( mRenderCoordinates.fromLocalCssToPix(AVERAGE_LINE_HEIGHT)); } @Override public void showHandle() { super.showHandle(); } }; mInsertionHandleController.hideAndDisallowAutomaticShowing(); } return mInsertionHandleController; } @VisibleForTesting public InsertionHandleController getInsertionHandleControllerForTest() { return mInsertionHandleController; } @VisibleForTesting public SelectionHandleController getSelectionHandleControllerForTest() { return mSelectionHandleController; } private void updateHandleScreenPositions() { if (isSelectionHandleShowing()) { mSelectionHandleController.setStartHandlePosition( mStartHandlePoint.getXPix(), mStartHandlePoint.getYPix()); mSelectionHandleController.setEndHandlePosition( mEndHandlePoint.getXPix(), mEndHandlePoint.getYPix()); } if (isInsertionHandleShowing()) { mInsertionHandleController.setHandlePosition( mInsertionHandlePoint.getXPix(), mInsertionHandlePoint.getYPix()); } } private void hideHandles() { if (mSelectionHandleController != null) { mSelectionHandleController.hideAndDisallowAutomaticShowing(); } if (mInsertionHandleController != null) { mInsertionHandleController.hideAndDisallowAutomaticShowing(); } mPositionObserver.removeListener(mPositionListener); } private void showSelectActionBar() { if (mActionMode != null) { mActionMode.invalidate(); return; } // Start a new action mode with a SelectActionModeCallback. SelectActionModeCallback.ActionHandler actionHandler = new SelectActionModeCallback.ActionHandler() { @Override public void selectAll() { mImeAdapter.selectAll(); } @Override public void cut() { mImeAdapter.cut(); } @Override public void copy() { mImeAdapter.copy(); } @Override public void paste() { mImeAdapter.paste(); } @Override public void share() { final String query = getSelectedText(); if (TextUtils.isEmpty(query)) return; Intent send = new Intent(Intent.ACTION_SEND); send.setType("text/plain"); send.putExtra(Intent.EXTRA_TEXT, query); try { Intent i = Intent.createChooser(send, getContext().getString( R.string.actionbar_share)); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getContext().startActivity(i); } catch (android.content.ActivityNotFoundException ex) { // If no app handles it, do nothing. } } @Override public void search() { final String query = getSelectedText(); if (TextUtils.isEmpty(query)) return; // See if ContentViewClient wants to override if (getContentViewClient().doesPerformWebSearch()) { getContentViewClient().performWebSearch(query); return; } Intent i = new Intent(Intent.ACTION_WEB_SEARCH); i.putExtra(SearchManager.EXTRA_NEW_SEARCH, true); i.putExtra(SearchManager.QUERY, query); i.putExtra(Browser.EXTRA_APPLICATION_ID, getContext().getPackageName()); if (!(getContext() instanceof Activity)) { i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } try { getContext().startActivity(i); } catch (android.content.ActivityNotFoundException ex) { // If no app handles it, do nothing. } } @Override public boolean isSelectionEditable() { return mSelectionEditable; } @Override public void onDestroyActionMode() { mActionMode = null; if (mUnselectAllOnActionModeDismiss) mImeAdapter.unselect(); getContentViewClient().onContextualActionBarHidden(); } @Override public boolean isShareAvailable() { Intent intent = new Intent(Intent.ACTION_SEND); intent.setType("text/plain"); return getContext().getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0; } @Override public boolean isWebSearchAvailable() { if (getContentViewClient().doesPerformWebSearch()) return true; Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); intent.putExtra(SearchManager.EXTRA_NEW_SEARCH, true); return getContext().getPackageManager().queryIntentActivities(intent, PackageManager.MATCH_DEFAULT_ONLY).size() > 0; } }; mActionMode = null; // On ICS, startActionMode throws an NPE when getParent() is null. if (mContainerView.getParent() != null) { mActionMode = mContainerView.startActionMode( getContentViewClient().getSelectActionModeCallback(getContext(), actionHandler, nativeIsIncognito(mNativeContentViewCore))); } mUnselectAllOnActionModeDismiss = true; if (mActionMode == null) { // There is no ActionMode, so remove the selection. mImeAdapter.unselect(); } else { getContentViewClient().onContextualActionBarShown(); } } public boolean getUseDesktopUserAgent() { if (mNativeContentViewCore != 0) { return nativeGetUseDesktopUserAgent(mNativeContentViewCore); } return false; } /** * Set whether or not we're using a desktop user agent for the currently loaded page. * @param override If true, use a desktop user agent. Use a mobile one otherwise. * @param reloadOnChange Reload the page if the UA has changed. */ public void setUseDesktopUserAgent(boolean override, boolean reloadOnChange) { if (mNativeContentViewCore != 0) { nativeSetUseDesktopUserAgent(mNativeContentViewCore, override, reloadOnChange); } } public void clearSslPreferences() { nativeClearSslPreferences(mNativeContentViewCore); } private boolean isSelectionHandleShowing() { return mSelectionHandleController != null && mSelectionHandleController.isShowing(); } private boolean isInsertionHandleShowing() { return mInsertionHandleController != null && mInsertionHandleController.isShowing(); } private void updateTextHandlesForGesture(int type) { switch(type) { case ContentViewGestureHandler.GESTURE_DOUBLE_TAP: case ContentViewGestureHandler.GESTURE_SCROLL_START: case ContentViewGestureHandler.GESTURE_FLING_START: case ContentViewGestureHandler.GESTURE_PINCH_BEGIN: temporarilyHideTextHandles(); break; default: break; } } // Makes the insertion/selection handles invisible. They will fade back in shortly after the // last call to scheduleTextHandleFadeIn (or temporarilyHideTextHandles). private void temporarilyHideTextHandles() { if (isSelectionHandleShowing() && !mSelectionHandleController.isDragging()) { mSelectionHandleController.setHandleVisibility(HandleView.INVISIBLE); } if (isInsertionHandleShowing() && !mInsertionHandleController.isDragging()) { mInsertionHandleController.setHandleVisibility(HandleView.INVISIBLE); } scheduleTextHandleFadeIn(); } private boolean allowTextHandleFadeIn() { if (mContentViewGestureHandler.isNativeScrolling() || mContentViewGestureHandler.isNativePinching()) { return false; } if (mPopupZoomer.isShowing()) return false; return true; } // Cancels any pending fade in and schedules a new one. private void scheduleTextHandleFadeIn() { if (!isInsertionHandleShowing() && !isSelectionHandleShowing()) return; if (mDeferredHandleFadeInRunnable == null) { mDeferredHandleFadeInRunnable = new Runnable() { @Override public void run() { if (!allowTextHandleFadeIn()) { // Delay fade in until it is allowed. scheduleTextHandleFadeIn(); } else { if (isSelectionHandleShowing()) { mSelectionHandleController.beginHandleFadeIn(); } if (isInsertionHandleShowing()) { mInsertionHandleController.beginHandleFadeIn(); } } } }; } mContainerView.removeCallbacks(mDeferredHandleFadeInRunnable); mContainerView.postDelayed(mDeferredHandleFadeInRunnable, TEXT_HANDLE_FADE_IN_DELAY); } /** * Shows the IME if the focused widget could accept text input. */ public void showImeIfNeeded() { if (mNativeContentViewCore != 0) nativeShowImeIfNeeded(mNativeContentViewCore); } @SuppressWarnings("unused") @CalledByNative private void updateFrameInfo( float scrollOffsetX, float scrollOffsetY, float pageScaleFactor, float minPageScaleFactor, float maxPageScaleFactor, float contentWidth, float contentHeight, float viewportWidth, float viewportHeight, float controlsOffsetYCss, float contentOffsetYCss, float overdrawBottomHeightCss) { TraceEvent.instant("ContentViewCore:updateFrameInfo"); // Adjust contentWidth/Height to be always at least as big as // the actual viewport (as set by onSizeChanged). contentWidth = Math.max(contentWidth, mRenderCoordinates.fromPixToLocalCss(mViewportWidthPix)); contentHeight = Math.max(contentHeight, mRenderCoordinates.fromPixToLocalCss(mViewportHeightPix)); final float contentOffsetYPix = mRenderCoordinates.fromDipToPix(contentOffsetYCss); final boolean contentSizeChanged = contentWidth != mRenderCoordinates.getContentWidthCss() || contentHeight != mRenderCoordinates.getContentHeightCss(); final boolean scaleLimitsChanged = minPageScaleFactor != mRenderCoordinates.getMinPageScaleFactor() || maxPageScaleFactor != mRenderCoordinates.getMaxPageScaleFactor(); final boolean pageScaleChanged = pageScaleFactor != mRenderCoordinates.getPageScaleFactor(); final boolean scrollChanged = pageScaleChanged || scrollOffsetX != mRenderCoordinates.getScrollX() || scrollOffsetY != mRenderCoordinates.getScrollY(); final boolean contentOffsetChanged = contentOffsetYPix != mRenderCoordinates.getContentOffsetYPix(); final boolean needHidePopupZoomer = contentSizeChanged || scrollChanged; final boolean needUpdateZoomControls = scaleLimitsChanged || scrollChanged; final boolean needTemporarilyHideHandles = scrollChanged; if (needHidePopupZoomer) mPopupZoomer.hide(true); if (scrollChanged) { mContainerViewInternals.onScrollChanged( (int) mRenderCoordinates.fromLocalCssToPix(scrollOffsetX), (int) mRenderCoordinates.fromLocalCssToPix(scrollOffsetY), (int) mRenderCoordinates.getScrollXPix(), (int) mRenderCoordinates.getScrollYPix()); } mRenderCoordinates.updateFrameInfo( scrollOffsetX, scrollOffsetY, contentWidth, contentHeight, viewportWidth, viewportHeight, pageScaleFactor, minPageScaleFactor, maxPageScaleFactor, contentOffsetYPix); onRenderCoordinatesUpdated(); if (needTemporarilyHideHandles) temporarilyHideTextHandles(); if (needUpdateZoomControls) mZoomControlsDelegate.updateZoomControls(); if (contentOffsetChanged) updateHandleScreenPositions(); // Update offsets for fullscreen. final float deviceScale = mRenderCoordinates.getDeviceScaleFactor(); final float controlsOffsetPix = controlsOffsetYCss * deviceScale; final float overdrawBottomHeightPix = overdrawBottomHeightCss * deviceScale; getContentViewClient().onOffsetsForFullscreenChanged( controlsOffsetPix, contentOffsetYPix, overdrawBottomHeightPix); mPendingRendererFrame = true; if (mBrowserAccessibilityManager != null) { mBrowserAccessibilityManager.notifyFrameInfoInitialized(); } // Update geometry for external video surface. getContentViewClient().onGeometryChanged(-1, null); } @CalledByNative private void updateImeAdapter(int nativeImeAdapterAndroid, int textInputType, String text, int selectionStart, int selectionEnd, int compositionStart, int compositionEnd, boolean showImeIfNeeded, boolean requireAck) { TraceEvent.begin(); mSelectionEditable = (textInputType != ImeAdapter.getTextInputTypeNone()); if (mActionMode != null) mActionMode.invalidate(); mImeAdapter.attachAndShowIfNeeded(nativeImeAdapterAndroid, textInputType, selectionStart, selectionEnd, showImeIfNeeded); if (mInputConnection != null) { mInputConnection.updateState(text, selectionStart, selectionEnd, compositionStart, compositionEnd, requireAck); } TraceEvent.end(); } @SuppressWarnings("unused") @CalledByNative private void setTitle(String title) { getContentViewClient().onUpdateTitle(title); } /** * Called (from native) when the <select> popup needs to be shown. * @param items Items to show. * @param enabled POPUP_ITEM_TYPEs for items. * @param multiple Whether the popup menu should support multi-select. * @param selectedIndices Indices of selected items. */ @SuppressWarnings("unused") @CalledByNative private void showSelectPopup(String[] items, int[] enabled, boolean multiple, int[] selectedIndices) { SelectPopupDialog.show(this, items, enabled, multiple, selectedIndices); } @SuppressWarnings("unused") @CalledByNative private void showDisambiguationPopup(Rect targetRect, Bitmap zoomedBitmap) { mPopupZoomer.setBitmap(zoomedBitmap); mPopupZoomer.show(targetRect); temporarilyHideTextHandles(); } @SuppressWarnings("unused") @CalledByNative private GenericTouchGesture createOnePointTouchGesture(int startX, int startY, int deltaX, int deltaY) { return new GenericTouchGesture(this, startX, startY, deltaX, deltaY); } @SuppressWarnings("unused") @CalledByNative private GenericTouchGesture createTwoPointTouchGesture( int startX0, int startY0, int deltaX0, int deltaY0, int startX1, int startY1, int deltaX1, int deltaY1) { return new GenericTouchGesture(this, startX0, startY0, deltaX0, deltaY0, startX1, startY1, deltaX1, deltaY1); } @SuppressWarnings("unused") @CalledByNative private void onSelectionChanged(String text) { mLastSelectedText = text; } @SuppressWarnings("unused") @CalledByNative private void onSelectionBoundsChanged(Rect anchorRectDip, int anchorDir, Rect focusRectDip, int focusDir, boolean isAnchorFirst) { // All coordinates are in DIP. int x1 = anchorRectDip.left; int y1 = anchorRectDip.bottom; int x2 = focusRectDip.left; int y2 = focusRectDip.bottom; if (x1 != x2 || y1 != y2 || (mSelectionHandleController != null && mSelectionHandleController.isDragging())) { if (mInsertionHandleController != null) { mInsertionHandleController.hide(); } if (isAnchorFirst) { mStartHandlePoint.setLocalDip(x1, y1); mEndHandlePoint.setLocalDip(x2, y2); } else { mStartHandlePoint.setLocalDip(x2, y2); mEndHandlePoint.setLocalDip(x1, y1); } boolean wereSelectionHandlesShowing = getSelectionHandleController().isShowing(); getSelectionHandleController().onSelectionChanged(anchorDir, focusDir); updateHandleScreenPositions(); mHasSelection = true; if (!wereSelectionHandlesShowing && getSelectionHandleController().isShowing()) { // TODO(cjhopman): Remove this when there is a better signal that long press caused // a selection. See http://crbug.com/150151. mContainerView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); } } else { mUnselectAllOnActionModeDismiss = false; hideSelectActionBar(); if (x1 != 0 && y1 != 0 && mSelectionEditable) { // Selection is a caret, and a text field is focused. if (mSelectionHandleController != null) { mSelectionHandleController.hide(); } mInsertionHandlePoint.setLocalDip(x1, y1); getInsertionHandleController().onCursorPositionChanged(); updateHandleScreenPositions(); InputMethodManager manager = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (manager.isWatchingCursor(mContainerView)) { final int xPix = (int) mInsertionHandlePoint.getXPix(); final int yPix = (int) mInsertionHandlePoint.getYPix(); manager.updateCursor(mContainerView, xPix, yPix, xPix, yPix); } } else { // Deselection if (mSelectionHandleController != null) { mSelectionHandleController.hideAndDisallowAutomaticShowing(); } if (mInsertionHandleController != null) { mInsertionHandleController.hideAndDisallowAutomaticShowing(); } } mHasSelection = false; } if (isSelectionHandleShowing() || isInsertionHandleShowing()) { mPositionObserver.addListener(mPositionListener); } } @SuppressWarnings("unused") @CalledByNative private static void onEvaluateJavaScriptResult( String jsonResult, JavaScriptCallback callback) { callback.handleJavaScriptResult(jsonResult); } @SuppressWarnings("unused") @CalledByNative private void showPastePopup(int xDip, int yDip) { mInsertionHandlePoint.setLocalDip(xDip, yDip); getInsertionHandleController().showHandle(); updateHandleScreenPositions(); getInsertionHandleController().showHandleWithPastePopup(); } @SuppressWarnings("unused") @CalledByNative private void onRenderProcessSwap(int oldPid, int newPid) { if (mAttachedToWindow && oldPid != newPid) { ChildProcessLauncher.getBindingManager().unbindAsHighPriority(oldPid); ChildProcessLauncher.getBindingManager().bindAsHighPriority(newPid); } // We want to remove the initial binding even if the ContentView is not attached, so that // renderers for ContentViews loading in background do not retain the high priority. ChildProcessLauncher.getBindingManager().removeInitialBinding(newPid); attachImeAdapter(); } @SuppressWarnings("unused") @CalledByNative private void onWebContentsConnected() { attachImeAdapter(); } /** * Attaches the native ImeAdapter object to the java ImeAdapter to allow communication via JNI. */ public void attachImeAdapter() { if (mImeAdapter != null && mNativeContentViewCore != 0) { mImeAdapter.attach(nativeGetNativeImeAdapter(mNativeContentViewCore)); } } /** * @see View#hasFocus() */ @CalledByNative public boolean hasFocus() { return mContainerView.hasFocus(); } /** * Checks whether the ContentViewCore can be zoomed in. * * @return True if the ContentViewCore can be zoomed in. */ // This method uses the term 'zoom' for legacy reasons, but relates // to what chrome calls the 'page scale factor'. public boolean canZoomIn() { final float zoomInExtent = mRenderCoordinates.getMaxPageScaleFactor() - mRenderCoordinates.getPageScaleFactor(); return zoomInExtent > ZOOM_CONTROLS_EPSILON; } /** * Checks whether the ContentViewCore can be zoomed out. * * @return True if the ContentViewCore can be zoomed out. */ // This method uses the term 'zoom' for legacy reasons, but relates // to what chrome calls the 'page scale factor'. public boolean canZoomOut() { final float zoomOutExtent = mRenderCoordinates.getPageScaleFactor() - mRenderCoordinates.getMinPageScaleFactor(); return zoomOutExtent > ZOOM_CONTROLS_EPSILON; } /** * Zooms in the ContentViewCore by 25% (or less if that would result in * zooming in more than possible). * * @return True if there was a zoom change, false otherwise. */ // This method uses the term 'zoom' for legacy reasons, but relates // to what chrome calls the 'page scale factor'. public boolean zoomIn() { if (!canZoomIn()) { return false; } return pinchByDelta(1.25f); } /** * Zooms out the ContentViewCore by 20% (or less if that would result in * zooming out more than possible). * * @return True if there was a zoom change, false otherwise. */ // This method uses the term 'zoom' for legacy reasons, but relates // to what chrome calls the 'page scale factor'. public boolean zoomOut() { if (!canZoomOut()) { return false; } return pinchByDelta(0.8f); } /** * Resets the zoom factor of the ContentViewCore. * * @return True if there was a zoom change, false otherwise. */ // This method uses the term 'zoom' for legacy reasons, but relates // to what chrome calls the 'page scale factor'. public boolean zoomReset() { // The page scale factor is initialized to mNativeMinimumScale when // the page finishes loading. Thus sets it back to mNativeMinimumScale. if (!canZoomOut()) return false; return pinchByDelta( mRenderCoordinates.getMinPageScaleFactor() / mRenderCoordinates.getPageScaleFactor()); } /** * Simulate a pinch zoom gesture. * * @param delta the factor by which the current page scale should be multiplied by. * @return whether the gesture was sent. */ public boolean pinchByDelta(float delta) { if (mNativeContentViewCore == 0) { return false; } long timeMs = System.currentTimeMillis(); int xPix = getViewportWidthPix() / 2; int yPix = getViewportHeightPix() / 2; getContentViewGestureHandler().pinchBegin(timeMs, xPix, yPix); getContentViewGestureHandler().pinchBy(timeMs, xPix, yPix, delta); getContentViewGestureHandler().pinchEnd(timeMs); return true; } /** * Invokes the graphical zoom picker widget for this ContentView. */ @Override public void invokeZoomPicker() { mZoomControlsDelegate.invokeZoomPicker(); } /** * This will mimic {@link #addPossiblyUnsafeJavascriptInterface(Object, String, Class)} * and automatically pass in {@link JavascriptInterface} as the required annotation. * * @param object The Java object to inject into the ContentViewCore's JavaScript context. Null * values are ignored. * @param name The name used to expose the instance in JavaScript. */ public void addJavascriptInterface(Object object, String name) { addPossiblyUnsafeJavascriptInterface(object, name, JavascriptInterface.class); } /** * This method injects the supplied Java object into the ContentViewCore. * The object is injected into the JavaScript context of the main frame, * using the supplied name. This allows the Java object to be accessed from * JavaScript. Note that that injected objects will not appear in * JavaScript until the page is next (re)loaded. For example: * <pre> view.addJavascriptInterface(new Object(), "injectedObject"); * view.loadData("<!DOCTYPE html><title></title>", "text/html", null); * view.loadUrl("javascript:alert(injectedObject.toString())");</pre> * <p><strong>IMPORTANT:</strong> * <ul> * <li> addJavascriptInterface() can be used to allow JavaScript to control * the host application. This is a powerful feature, but also presents a * security risk. Use of this method in a ContentViewCore containing * untrusted content could allow an attacker to manipulate the host * application in unintended ways, executing Java code with the permissions * of the host application. Use extreme care when using this method in a * ContentViewCore which could contain untrusted content. Particular care * should be taken to avoid unintentional access to inherited methods, such * as {@link Object#getClass()}. To prevent access to inherited methods, * pass an annotation for {@code requiredAnnotation}. This will ensure * that only methods with {@code requiredAnnotation} are exposed to the * Javascript layer. {@code requiredAnnotation} will be passed to all * subsequently injected Java objects if any methods return an object. This * means the same restrictions (or lack thereof) will apply. Alternatively, * {@link #addJavascriptInterface(Object, String)} can be called, which * automatically uses the {@link JavascriptInterface} annotation. * <li> JavaScript interacts with Java objects on a private, background * thread of the ContentViewCore. Care is therefore required to maintain * thread safety.</li> * </ul></p> * * @param object The Java object to inject into the * ContentViewCore's JavaScript context. Null * values are ignored. * @param name The name used to expose the instance in * JavaScript. * @param requiredAnnotation Restrict exposed methods to ones with this * annotation. If {@code null} all methods are * exposed. * */ public void addPossiblyUnsafeJavascriptInterface(Object object, String name, Class<? extends Annotation> requiredAnnotation) { if (mNativeContentViewCore != 0 && object != null) { mJavaScriptInterfaces.put(name, object); nativeAddJavascriptInterface(mNativeContentViewCore, object, name, requiredAnnotation, mRetainedJavaScriptObjects); } } /** * Removes a previously added JavaScript interface with the given name. * * @param name The name of the interface to remove. */ public void removeJavascriptInterface(String name) { mJavaScriptInterfaces.remove(name); if (mNativeContentViewCore != 0) { nativeRemoveJavascriptInterface(mNativeContentViewCore, name); } } /** * Return the current scale of the ContentView. * @return The current page scale factor. */ public float getScale() { return mRenderCoordinates.getPageScaleFactor(); } /** * If the view is ready to draw contents to the screen. In hardware mode, * the initialization of the surface texture may not occur until after the * view has been added to the layout. This method will return {@code true} * once the texture is actually ready. */ public boolean isReady() { return nativeIsRenderWidgetHostViewReady(mNativeContentViewCore); } @CalledByNative private void startContentIntent(String contentUrl) { getContentViewClient().onStartContentIntent(getContext(), contentUrl); } @Override public void onAccessibilityStateChanged(boolean enabled) { setAccessibilityState(enabled); } /** * Determines whether or not this ContentViewCore can handle this accessibility action. * @param action The action to perform. * @return Whether or not this action is supported. */ public boolean supportsAccessibilityAction(int action) { return mAccessibilityInjector.supportsAccessibilityAction(action); } /** * Attempts to perform an accessibility action on the web content. If the accessibility action * cannot be processed, it returns {@code null}, allowing the caller to know to call the * super {@link View#performAccessibilityAction(int, Bundle)} method and use that return value. * Otherwise the return value from this method should be used. * @param action The action to perform. * @param arguments Optional action arguments. * @return Whether the action was performed or {@code null} if the call should be delegated to * the super {@link View} class. */ public boolean performAccessibilityAction(int action, Bundle arguments) { if (mAccessibilityInjector.supportsAccessibilityAction(action)) { return mAccessibilityInjector.performAccessibilityAction(action, arguments); } return false; } /** * Set the BrowserAccessibilityManager, used for native accessibility * (not script injection). This is only set when system accessibility * has been enabled. * @param manager The new BrowserAccessibilityManager. */ public void setBrowserAccessibilityManager(BrowserAccessibilityManager manager) { mBrowserAccessibilityManager = manager; } /** * Get the BrowserAccessibilityManager, used for native accessibility * (not script injection). This will return null when system accessibility * is not enabled. * @return This view's BrowserAccessibilityManager. */ public BrowserAccessibilityManager getBrowserAccessibilityManager() { return mBrowserAccessibilityManager; } /** * If native accessibility (not script injection) is enabled, and if this is * running on JellyBean or later, returns an AccessibilityNodeProvider that * implements native accessibility for this view. Returns null otherwise. * Lazily initializes native accessibility here if it's allowed. * @return The AccessibilityNodeProvider, if available, or null otherwise. */ public AccessibilityNodeProvider getAccessibilityNodeProvider() { if (mBrowserAccessibilityManager != null) { return mBrowserAccessibilityManager.getAccessibilityNodeProvider(); } if (mNativeAccessibilityAllowed && !mNativeAccessibilityEnabled && mNativeContentViewCore != 0 && Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { mNativeAccessibilityEnabled = true; nativeSetAccessibilityEnabled(mNativeContentViewCore, true); } return null; } /** * @see View#onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo) */ public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) { // Note: this is only used by the script-injecting accessibility code. mAccessibilityInjector.onInitializeAccessibilityNodeInfo(info); } /** * @see View#onInitializeAccessibilityEvent(AccessibilityEvent) */ public void onInitializeAccessibilityEvent(AccessibilityEvent event) { // Note: this is only used by the script-injecting accessibility code. event.setClassName(this.getClass().getName()); // Identify where the top-left of the screen currently points to. event.setScrollX(mRenderCoordinates.getScrollXPixInt()); event.setScrollY(mRenderCoordinates.getScrollYPixInt()); // The maximum scroll values are determined by taking the content dimensions and // subtracting off the actual dimensions of the ChromeView. int maxScrollXPix = Math.max(0, mRenderCoordinates.getMaxHorizontalScrollPixInt()); int maxScrollYPix = Math.max(0, mRenderCoordinates.getMaxVerticalScrollPixInt()); event.setScrollable(maxScrollXPix > 0 || maxScrollYPix > 0); // Setting the maximum scroll values requires API level 15 or higher. final int SDK_VERSION_REQUIRED_TO_SET_SCROLL = 15; if (Build.VERSION.SDK_INT >= SDK_VERSION_REQUIRED_TO_SET_SCROLL) { event.setMaxScrollX(maxScrollXPix); event.setMaxScrollY(maxScrollYPix); } } /** * Returns whether accessibility script injection is enabled on the device */ public boolean isDeviceAccessibilityScriptInjectionEnabled() { try { if (!mContentSettings.getJavaScriptEnabled()) { return false; } int result = getContext().checkCallingOrSelfPermission( android.Manifest.permission.INTERNET); if (result != PackageManager.PERMISSION_GRANTED) { return false; } Field field = Settings.Secure.class.getField("ACCESSIBILITY_SCRIPT_INJECTION"); field.setAccessible(true); String accessibilityScriptInjection = (String) field.get(null); ContentResolver contentResolver = getContext().getContentResolver(); if (mAccessibilityScriptInjectionObserver == null) { ContentObserver contentObserver = new ContentObserver(new Handler()) { @Override public void onChange(boolean selfChange, Uri uri) { setAccessibilityState(mAccessibilityManager.isEnabled()); } }; contentResolver.registerContentObserver( Settings.Secure.getUriFor(accessibilityScriptInjection), false, contentObserver); mAccessibilityScriptInjectionObserver = contentObserver; } return Settings.Secure.getInt(contentResolver, accessibilityScriptInjection, 0) == 1; } catch (NoSuchFieldException e) { } catch (IllegalAccessException e) { } return false; } /** * Returns whether or not accessibility injection is being used. */ public boolean isInjectingAccessibilityScript() { return mAccessibilityInjector.accessibilityIsAvailable(); } /** * Turns browser accessibility on or off. * If |state| is |false|, this turns off both native and injected accessibility. * Otherwise, if accessibility script injection is enabled, this will enable the injected * accessibility scripts. Native accessibility is enabled on demand. */ public void setAccessibilityState(boolean state) { if (!state) { setInjectedAccessibility(false); mNativeAccessibilityAllowed = false; } else { boolean useScriptInjection = isDeviceAccessibilityScriptInjectionEnabled(); setInjectedAccessibility(useScriptInjection); mNativeAccessibilityAllowed = !useScriptInjection; } } /** * Enable or disable injected accessibility features */ public void setInjectedAccessibility(boolean enabled) { mAccessibilityInjector.addOrRemoveAccessibilityApisIfNecessary(); mAccessibilityInjector.setScriptEnabled(enabled); } /** * Stop any TTS notifications that are currently going on. */ public void stopCurrentAccessibilityNotifications() { mAccessibilityInjector.onPageLostFocus(); } /** * Inform WebKit that Fullscreen mode has been exited by the user. */ public void exitFullscreen() { if (mNativeContentViewCore != 0) nativeExitFullscreen(mNativeContentViewCore); } /** * Changes whether hiding the top controls is enabled. * * @param enableHiding Whether hiding the top controls should be enabled or not. * @param enableShowing Whether showing the top controls should be enabled or not. * @param animate Whether the transition should be animated or not. */ public void updateTopControlsState(boolean enableHiding, boolean enableShowing, boolean animate) { if (mNativeContentViewCore != 0) { nativeUpdateTopControlsState( mNativeContentViewCore, enableHiding, enableShowing, animate); } } /** * Callback factory method for nativeGetNavigationHistory(). */ @CalledByNative private void addToNavigationHistory(Object history, int index, String url, String virtualUrl, String originalUrl, String title, Bitmap favicon) { NavigationEntry entry = new NavigationEntry( index, url, virtualUrl, originalUrl, title, favicon); ((NavigationHistory) history).addEntry(entry); } /** * Get a copy of the navigation history of the view. */ public NavigationHistory getNavigationHistory() { NavigationHistory history = new NavigationHistory(); if (mNativeContentViewCore != 0) { int currentIndex = nativeGetNavigationHistory(mNativeContentViewCore, history); history.setCurrentEntryIndex(currentIndex); } return history; } @Override public NavigationHistory getDirectedNavigationHistory(boolean isForward, int itemLimit) { NavigationHistory history = new NavigationHistory(); if (mNativeContentViewCore != 0) { nativeGetDirectedNavigationHistory( mNativeContentViewCore, history, isForward, itemLimit); } return history; } /** * @return The original request URL for the current navigation entry, or null if there is no * current entry. */ public String getOriginalUrlForActiveNavigationEntry() { if (mNativeContentViewCore != 0) { return nativeGetOriginalUrlForActiveNavigationEntry(mNativeContentViewCore); } return ""; } /** * @return The cached copy of render positions and scales. */ public RenderCoordinates getRenderCoordinates() { return mRenderCoordinates; } @CalledByNative private int getLocationInWindowX() { return mLocationInWindowX; } @CalledByNative private int getLocationInWindowY() { return mLocationInWindowY; } @CalledByNative private static Rect createRect(int x, int y, int right, int bottom) { return new Rect(x, y, right, bottom); } public void attachExternalVideoSurface(int playerId, Surface surface) { if (mNativeContentViewCore != 0) { nativeAttachExternalVideoSurface(mNativeContentViewCore, playerId, surface); } } public void detachExternalVideoSurface(int playerId) { if (mNativeContentViewCore != 0) { nativeDetachExternalVideoSurface(mNativeContentViewCore, playerId); } } private boolean onAnimate(long frameTimeMicros) { if (mNativeContentViewCore == 0) return false; return nativeOnAnimate(mNativeContentViewCore, frameTimeMicros); } private void animateIfNecessary(long frameTimeMicros) { if (mNeedAnimate) { mNeedAnimate = onAnimate(frameTimeMicros); if (!mNeedAnimate) removeVSyncSubscriber(); } } @CalledByNative private void notifyExternalSurface( int playerId, boolean isRequest, float x, float y, float width, float height) { if (isRequest) getContentViewClient().onExternalVideoSurfaceRequested(playerId); getContentViewClient().onGeometryChanged(playerId, new RectF(x, y, x + width, y + height)); } /** * Offer a subset of gesture events to the embedding View, * primarily for WebView compatibility. * * @param type The type of the event. * * @return true if the embedder handled the event. */ private boolean offerGestureToEmbedder(int type) { if (type == ContentViewGestureHandler.GESTURE_LONG_PRESS) { return mContainerView.performLongClick(); } return false; } private native int nativeInit(boolean hardwareAccelerated, int webContentsPtr, int viewAndroidPtr, int windowAndroidPtr); @CalledByNative private ContentVideoViewClient getContentVideoViewClient() { return mContentViewClient.getContentVideoViewClient(); } private native void nativeOnJavaContentViewCoreDestroyed(int nativeContentViewCoreImpl); private native void nativeLoadUrl( int nativeContentViewCoreImpl, String url, int loadUrlType, int transitionType, int uaOverrideOption, String extraHeaders, byte[] postData, String baseUrlForDataUrl, String virtualUrlForDataUrl, boolean canLoadLocalResources); private native String nativeGetURL(int nativeContentViewCoreImpl); private native String nativeGetTitle(int nativeContentViewCoreImpl); private native void nativeShowInterstitialPage( int nativeContentViewCoreImpl, String url, int nativeInterstitialPageDelegateAndroid); private native boolean nativeIsShowingInterstitialPage(int nativeContentViewCoreImpl); private native boolean nativeIsIncognito(int nativeContentViewCoreImpl); private native void nativeSetFocus(int nativeContentViewCoreImpl, boolean focused); private native void nativeSendOrientationChangeEvent( int nativeContentViewCoreImpl, int orientation); // All touch events (including flings, scrolls etc) accept coordinates in physical pixels. private native boolean nativeSendTouchEvent( int nativeContentViewCoreImpl, long timeMs, int action, TouchPoint[] pts); private native int nativeSendMouseMoveEvent( int nativeContentViewCoreImpl, long timeMs, float x, float y); private native int nativeSendMouseWheelEvent( int nativeContentViewCoreImpl, long timeMs, float x, float y, float verticalAxis); private native void nativeScrollBegin( int nativeContentViewCoreImpl, long timeMs, float x, float y); private native void nativeScrollEnd(int nativeContentViewCoreImpl, long timeMs); private native void nativeScrollBy( int nativeContentViewCoreImpl, long timeMs, float x, float y, float deltaX, float deltaY); private native void nativeFlingStart( int nativeContentViewCoreImpl, long timeMs, float x, float y, float vx, float vy); private native void nativeFlingCancel(int nativeContentViewCoreImpl, long timeMs); private native void nativeSingleTap( int nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap); private native void nativeSingleTapUnconfirmed( int nativeContentViewCoreImpl, long timeMs, float x, float y); private native void nativeShowPressState( int nativeContentViewCoreImpl, long timeMs, float x, float y); private native void nativeTapCancel( int nativeContentViewCoreImpl, long timeMs, float x, float y); private native void nativeTapDown( int nativeContentViewCoreImpl, long timeMs, float x, float y); private native void nativeDoubleTap( int nativeContentViewCoreImpl, long timeMs, float x, float y); private native void nativeLongPress( int nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap); private native void nativeLongTap( int nativeContentViewCoreImpl, long timeMs, float x, float y, boolean linkPreviewTap); private native void nativePinchBegin( int nativeContentViewCoreImpl, long timeMs, float x, float y); private native void nativePinchEnd(int nativeContentViewCoreImpl, long timeMs); private native void nativePinchBy(int nativeContentViewCoreImpl, long timeMs, float anchorX, float anchorY, float deltaScale); private native void nativeSelectBetweenCoordinates( int nativeContentViewCoreImpl, float x1, float y1, float x2, float y2); private native void nativeMoveCaret(int nativeContentViewCoreImpl, float x, float y); private native boolean nativeCanGoBack(int nativeContentViewCoreImpl); private native boolean nativeCanGoForward(int nativeContentViewCoreImpl); private native boolean nativeCanGoToOffset(int nativeContentViewCoreImpl, int offset); private native void nativeGoBack(int nativeContentViewCoreImpl); private native void nativeGoForward(int nativeContentViewCoreImpl); private native void nativeGoToOffset(int nativeContentViewCoreImpl, int offset); private native void nativeGoToNavigationIndex(int nativeContentViewCoreImpl, int index); private native void nativeLoadIfNecessary(int nativeContentViewCoreImpl); private native void nativeRequestRestoreLoad(int nativeContentViewCoreImpl); private native void nativeStopLoading(int nativeContentViewCoreImpl); private native void nativeReload(int nativeContentViewCoreImpl, boolean checkForRepost); private native void nativeReloadIgnoringCache( int nativeContentViewCoreImpl, boolean checkForRepost); private native void nativeCancelPendingReload(int nativeContentViewCoreImpl); private native void nativeContinuePendingReload(int nativeContentViewCoreImpl); private native void nativeSelectPopupMenuItems(int nativeContentViewCoreImpl, int[] indices); private native void nativeScrollFocusedEditableNodeIntoView(int nativeContentViewCoreImpl); private native void nativeUndoScrollFocusedEditableNodeIntoView(int nativeContentViewCoreImpl); private native void nativeClearHistory(int nativeContentViewCoreImpl); private native void nativeEvaluateJavaScript(int nativeContentViewCoreImpl, String script, JavaScriptCallback callback, boolean startRenderer); private native int nativeGetNativeImeAdapter(int nativeContentViewCoreImpl); private native int nativeGetCurrentRenderProcessId(int nativeContentViewCoreImpl); private native int nativeGetBackgroundColor(int nativeContentViewCoreImpl); private native void nativeOnShow(int nativeContentViewCoreImpl); private native void nativeOnHide(int nativeContentViewCoreImpl); private native void nativeSetUseDesktopUserAgent(int nativeContentViewCoreImpl, boolean enabled, boolean reloadOnChange); private native boolean nativeGetUseDesktopUserAgent(int nativeContentViewCoreImpl); private native void nativeClearSslPreferences(int nativeContentViewCoreImpl); private native void nativeAddJavascriptInterface(int nativeContentViewCoreImpl, Object object, String name, Class requiredAnnotation, HashSet<Object> retainedObjectSet); private native void nativeRemoveJavascriptInterface(int nativeContentViewCoreImpl, String name); private native int nativeGetNavigationHistory(int nativeContentViewCoreImpl, Object context); private native void nativeGetDirectedNavigationHistory(int nativeContentViewCoreImpl, Object context, boolean isForward, int maxEntries); private native String nativeGetOriginalUrlForActiveNavigationEntry( int nativeContentViewCoreImpl); private native void nativeUpdateVSyncParameters(int nativeContentViewCoreImpl, long timebaseMicros, long intervalMicros); private native void nativeOnVSync(int nativeContentViewCoreImpl, long frameTimeMicros); private native boolean nativeOnAnimate(int nativeContentViewCoreImpl, long frameTimeMicros); private native boolean nativePopulateBitmapFromCompositor(int nativeContentViewCoreImpl, Bitmap bitmap); private native void nativeWasResized(int nativeContentViewCoreImpl); private native boolean nativeIsRenderWidgetHostViewReady(int nativeContentViewCoreImpl); private native void nativeExitFullscreen(int nativeContentViewCoreImpl); private native void nativeUpdateTopControlsState(int nativeContentViewCoreImpl, boolean enableHiding, boolean enableShowing, boolean animate); private native void nativeShowImeIfNeeded(int nativeContentViewCoreImpl); private native void nativeAttachExternalVideoSurface( int nativeContentViewCoreImpl, int playerId, Surface surface); private native void nativeDetachExternalVideoSurface( int nativeContentViewCoreImpl, int playerId); private native void nativeSetAccessibilityEnabled( int nativeContentViewCoreImpl, boolean enabled); private native void nativeSendSingleTapUma(int nativeContentViewCoreImpl, int type, int count); private native void nativeSendActionAfterDoubleTapUma(int nativeContentViewCoreImpl, int type, boolean hasDelay, int count); }